[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CVJD5CWTBPD284WQST5BCE":3,"skill-same:01M6CVJD5CWTBPD284WQST5BCE":81},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":33,"linkOnly":34,"markdownContent":35,"markdownBody":36,"frontMatter":37,"refName":39,"commitSha":40,"skillDirectory":41,"forksCount":42,"repositoryHtmlUrl":43,"repositoryHomepageUrl":6,"ownerHtmlUrl":44,"ownerBlogUrl":45,"ownerDescription":6,"updatedAt":46,"install":47,"resources":60},"01M6CVJD5CWTBPD284WQST5BCE","grading-shared",null,"Shared configuration and protocols for all grading workflows","georgernstgraf\u002Fopencode-helpers","georgernstgraf","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F115418377?v=4","01M6CVHJ34JS0CF98EFFN0FZ76","Georg Graf SPG","01M6CVHJ7VV5VK6RXY62SR0NE2",1,"MIT","https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers\u002Fblob\u002Fmain\u002Fskills\u002Fgrading-shared\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000039","教师与教育","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32],"grading shared","grading","shared","workflows","protocols","opencode","configuration","education","for","2026-11-10 10:12:30",false,"---\nname: grading-shared\ndescription: Shared configuration and protocols for all grading workflows\nlicense: MIT\ncompatibility: opencode\nmetadata:\n  category: education\n  output: configuration\n---\n\n# Grading Shared Configuration\n\n## Purpose\n\nThis skill provides centralized configuration for all grading workflows,\nensuring consistent email formatting, address styles, second-person address\nthroughout all grading content, and database access across different\nassessment types.\n\n## Output Language: MANDATORY GERMAN\n\nAll student-facing content (grading reports, feedback, email bodies) MUST be \nwritten in natural German with proper UTF-8 umlauts (ä, ö, ü, ß). \nNever use English for content intended for students.\n\n## Usage\n\nImport this skill at the beginning of any grading-related skill or command.\nReference the sections below for consistent behavior.\n\n## Anrede-Gate (CRITICAL, Pflicht)\n\nDie Anrede (Du\u002FSie) wird **nicht** aus dem Klassennamen und **nicht** aus der\nDatenbank abgeleitet. Sie steht in genau einer Datei pro Kohorte:\n\n```\n\u003Csubject>-T\u002F\u003Ckohorte>\u002Fanrede.txt\n```\n\nDie Datei enthält genau ein Wort: `Sie` oder `Du`.\n\nAblauf:\n\n1. `-T`-Repo-Wurzel und Kohorten-Ordner bestimmen (der Lauf-Ordner\n   `\u003Ckohorte>\u002F\u003CYYYY-MM-DD>_\u003Cslug>\u002F` liegt darin; `_class` zeigt auf das\n   öffentliche Schwester-Repo).\n2. Anrede mit dem mitgelieferten Skript lesen:\n\n   ```bash\n   ANREDE=$(~\u002F.opencode\u002Fskills\u002Fgrading-shared\u002Fread-anrede.sh \\\n       \"$T_REPO\u002F$KOHORTE\u002Fanrede.txt\") || exit 1\n   ```\n\n3. `Du` → informell, `Sie` → formell. Die Anrede gilt für **alle**\n   studentengerichteten Texte (E-Mails, `*_grading.md`, `GRADINGS.md`,\n   `CLASS.md`).\n\n**Stop-Regel (CRITICAL):** Fehlt die Datei, ist sie leer oder enthält sie\netwas anderes als `Sie`\u002F`Du`, sofort **STOPPEN** — vor dem ersten Schreibzugriff\n— und den Pfad samt Soll-Inhalt auf Deutsch melden:\n\n```\nFEHLT: \u002Fhome\u002Fgeorg\u002Fr\u002FGRG-WMC-T\u002F3aaif\u002Fanrede.txt\nBitte die Datei anlegen und genau 'Sie' oder 'Du' hineinschreiben.\n```\n\nEs gibt **keinen** Fallback auf eine Klassenliste und kein Raten. Das Gate gilt\nfür `repograde`, `knowledge-assessment`, `knowledge-exam` und `projectgrade`;\nes läuft immer vor dem ersten Schreibzugriff eines Laufs.\n\n## Lauf-Umgebung (Grading)\n\nRepositorien-basierte Läufe (`repograde`) starten im Grading-Arbeitsverzeichnis\n`~\u002Fgits\u002F\u003Ckohorte>-\u003Cfach>\u002F` (z. B. `~\u002Fgits\u002F3aaif-wmc\u002F`). Das ist **kein**\nGit-Repo; es enthält:\n\n```\n~\u002Fgits\u002F3aaif-wmc\u002F_class            -> \u002Fhome\u002Fgeorg\u002Fr\u002FGRG-WMC\u002F3aaif\n~\u002Fgits\u002F3aaif-wmc\u002FAbazi Hamza       -> Hamza473A        (Namens-Symlink)\n~\u002Fgits\u002F3aaif-wmc\u002FHamza473A\u002F        (Schüler-Repo, gelesen)\n```\n\n- `_class` zeigt in den öffentlichen Klassenordner des Teaching-Repos. Immer\n  mit `readlink -f` auflösen: `~\u002Fr` ist selbst ein Symlink auf\n  `repos\u002Fgeorgernstgraf\u002F`, und das Ziel kann mit oder ohne Schrägstrich enden.\n- Das private Test-Repo ist das **Schwesterverzeichnis** mit Suffix `-T`:\n  `\u002F…\u002FGRG-\u003CFACH>\u002F\u003Ckohorte>` → `\u002F…\u002FGRG-\u003CFACH>-T`.\n- Lauf-Ordner (alle Ausgaben eines Laufs, inkl. `noten.tsv`):\n  `\u003C…>-T\u002F\u003Ckohorte>\u002F\u003CYYYY-MM-DD>_\u003Cslug>\u002F` — bei Bedarf anlegen.\n- Anrede-Datei: `\u003C…>-T\u002F\u003Ckohorte>\u002Fanrede.txt`.\n- `roster.tsv` (aus `wu … roster`) ist ein Zwischenprodukt und liegt unter\n  `\u002Ftmp\u002Fopencode\u002F`, nie im Lauf-Ordner und nie im Commit.\n- `knowledge-assessment` läuft dagegen direkt im `-T`-Repo; der Lauf-Ordner\n  `\u003Ckohorte>\u002F\u003CYYYY-MM-DD>_\u003Cslug>\u002F` ist dort CWD bzw. wird angelegt.\n\n## Class Configuration\n\n### Name Parsing from Repository Basename (CRITICAL)\n\nRepository directory names follow the convention `Lastname Firstname`. The **last\nword** is always the first name; **everything before it** is the last name. This\napplies to compound last names as well.\n\n**Parsing rule:** Split the basename on spaces → `parts[0:-1]` = last name,\n`parts[-1]` = first name.\n\n| Repository Basename | Last Name | First Name |\n|---------------------|-----------|------------|\n| `Umlauf Ellen` | Umlauf | Ellen |\n| `Quintero Castañeda Nicolas` | Quintero Castañeda | Nicolas |\n| `Prodanovic Jovana` | Prodanovic | Jovana |\n| `Wahringer Tobias` | Wahringer | Tobias |\n| `Leeb Samuel` | Leeb | Samuel |\n| `Zore Elsner Marija` | Zore Elsner | Marija |\n\n**Wrong vs Correct — Formal salutation:**\n```\n❌ Sehr geehrte Frau Ellen,        ← used first name as last name\n✅ Sehr geehrte Frau Umlauf,       ← correct last name\n\n❌ Sehr geehrter Herr Nicolas,      ← used first name as last name\n✅ Sehr geehrter Herr Quintero Castañeda,  ← correct compound last name\n\n❌ Sehr geehrter Herr Tobias,       ← used first name as last name\n✅ Sehr geehrter Herr Wahringer,    ← correct last name\n```\n\n**Wrong vs Correct — Informal salutation:**\n```\n❌ Liebe Umlauf,                    ← used last name as first name\n✅ Liebe Ellen,                     ← correct first name\n\n❌ Lieber Quintero Castañeda,       ← used last name as first name\n✅ Lieber Nicolas,                  ← correct first name\n```\n\n## Email Generation Protocol\n\n### Greeting Formulas\n\n**Formal Address** (uses **last name** — see Name Parsing section above):\n- Female: `Sehr geehrte Frau [Last Name],`\n- Male: `Sehr geehrter Herr [Last Name],`\n- Unknown gender: `Guten Tag [First Name] [Last Name],`\n\n**Informal Address** (uses **first name** — the last word in the basename):\n- Any gender: `Liebe [First Name],` or `Lieber [First Name],`\n- Female preference: `Liebe [First Name],`\n- Male preference: `Lieber [First Name],`\n- Unknown gender: `Guten Tag [First Name] [Last Name],`\n\n**Concrete examples:**\n\n| Basename | Gender | Formal Greeting | Informal Greeting |\n|----------|--------|-----------------|-------------------|\n| `Umlauf Ellen` | F | `Sehr geehrte Frau Umlauf,` | `Liebe Ellen,` |\n| `Quintero Castañeda Nicolas` | M | `Sehr geehrter Herr Quintero Castañeda,` | `Lieber Nicolas,` |\n| `Wahringer Tobias` | M | `Sehr geehrter Herr Wahringer,` | `Lieber Tobias,` |\n\n### Closing Formulas\n\n**Formal Address:**\n```\nMit freundlichen Grüßen,\n\n   Georg Graf\n```\n\n**Informal Address:**\n```\nLieben Gruß,\n\n   Georg Graf\n```\n\n### Automatic Assessment Disclaimers\n\nEvery grading email body MUST include a disclaimer paragraph directly below the greeting. The wording depends on the class's address style (Formal\u002FInformal) and the assessment type (Assignments\u002FProjects vs. Knowledge-Checks):\n\n**Formal Address (Sie):**\n- **Assignments \u002F Projects \u002F General (Leistungsfeststellung):**\n  `Im Folgenden finden Sie die automatische Beurteilung Ihrer Leistungsfeststellung. Bitte beachten Sie, dass diese Beurteilung Irrtümer enthalten kann.`\n- **Knowledge-Checks \u002F Mini-Exams (Knowledge-Check):**\n  `Im Folgenden finden Sie die automatische Beurteilung Ihres Knowledge-Checks. Bitte beachten Sie, dass diese Beurteilung Irrtümer enthalten kann.`\n\n**Informal Address (Du):**\n- **Assignments \u002F Projects \u002F General (Leistungsfeststellung):**\n  `Im Folgenden findest du die automatische Beurteilung deiner Leistungsfeststellung. Bitte beachte, dass diese Beurteilung Irrtümer enthalten kann.`\n- **Knowledge-Checks \u002F Mini-Exams (Knowledge-Check):**\n  `Im Folgenden findest du die automatische Beurteilung deines Knowledge-Checks. Bitte beachte, dass diese Beurteilung Irrtümer enthalten kann.`\n\n### Gender Determination\n\nGender is determined from the **first name** (the last word in the repository\nbasename). Do NOT use the full basename string for gender lookup.\n\nWhen gender is not clear from the first name:\n1. Use class records or submission wording as additional context.\n2. If still uncertain, use neutral fallback: `Guten Tag [First Name] [Last Name],`\n3. Keep class-based closing formula (formal or informal based on class).\n4. Flag case for manual review in the output files.\n\n## Second-Person Address in Grading Content\n\n### Requirement\n\nALL grading content must address the student directly in the second person,\nmatching the email salutation style. This applies to:\n- `*_grading.md` files (repograde, knowledge-assessment, projectgrade)\n- Email body text in `EMAIL.json`\n\n### Pronoun and Verb conjugation\n\n| Style | Pronoun | Verb Conjugation | Example |\n|-------|---------|-----------------|---------|\n| Formal (Sie) | Sie | 3rd person plural formal | \"Sie haben die Abgabe gut gelöst\" |\n| Informal (Du) | Du | 2nd person informal | \"Du hast die Abgabe gut gelöst\" |\n\n### Grading Content Examples\n\n**Formal Address (Sie) - Wrong ❌:**\n```\nDer Student hat die Abgabe gut gelöst. Er hat sich bemüht.\n```\n\n**Formal Address (Sie) - Correct ✅:**\n```\nSie haben die Abgabe gut gelöst. Sie haben sich bemüht.\n```\n\n**Informal Address (Du) - Wrong ❌:**\n```\nDer Schüler hat die Abgabe gut gelöst. Er hat sich bemüht.\n```\n\n**Informal Address (Du) - Correct ✅:**\n```\nDu hast die Abgabe gut gelöst. Du hast dich bemüht.\n```\n\n### Gender-Neutral Handling for Unclear Cases\n\nWhen student gender cannot be determined:\n1. Use formal \"Sie\" (works as gender-neutral in written German).\n2. Use gender-neutral adjective forms where possible.\n3. Use neutral greeting fallback: `Guten Tag [First Name] [Last Name],`\n4. Flag in output files for manual review.\n\n### Consistency Checklist\n\nBefore finalizing any grading output, verify:\n- [ ] Email greeting matches body address style (Sie or Du)\n- [ ] All pronouns in body refer to student as \"Sie\" or \"Du\"\n- [ ] No third-person references to the student (\"der Schüler\", \"die Studentin\")\n- [ ] Verb conjugation matches address style\n- [ ] Closing formula matches address style\n\n## Grading Methodology (CRITICAL)\n\nAll grading workflows MUST evaluate student work through pure AI reasoning and\njudgment. The following rules apply to every grading skill (`repograde`,\n`knowledge-assessment`, `projectgrade`) and every subagent used within them.\n\n### Pure AI Evaluation\n\n- **Grading is performed through AI reasoning alone.** The agent must read,\n  understand, and evaluate each student submission by directly comparing it\n  against the task instructions and expected solutions. The evaluation must be\n  a direct intellectual act by the AI, not a mechanical or automated process.\n- **No scripts or programs for evaluation.** Under no circumstances may agents\n  write, invoke, or rely on scripts, programs, test runners, linters, or\n  automated checkers to assess student work. Grading must never be reduced to\n  a mechanical operation.\n- **Programmatic output formatting is allowed.** Using `json.dump()` or similar\n  serializers to write `EMAIL.json` or `*_email.json` files is permitted. The\n  prohibition applies only to the act of grading\u002Fassessing the student's work\n  itself — not to output formatting helpers.\n\n### Available Inputs Per Evaluation\n\nFor every student submission, the agent has access to the following inputs and\nmust base its evaluation exclusively on them:\n\n1. **Task instructions** — the assignment description (`Aufgabe.md` or the\n   `## Aufgabe` section of a Tages-README), exam questions, or project\n   requirements that define what was asked of the student.\n2. **Expected solutions** — the reference solution or answer key against which\n   student work is compared.\n3. **Student submission** — the individual student's work (repository commits,\n   answer file, code changes) to be evaluated.\n\n### Handling Large Volumes\n\n- **For bulk or multi-student grading, use sub-agents.** Each sub-agent\n  evaluates one student or repository independently, using the same pure AI\n  reasoning approach.\n- Sub-agents MUST follow all rules defined in this skill, including the\n  no-script grading requirement.\n- The orchestrating agent delegates work to sub-agents but must never write\n  evaluation scripts or batch-processing programs.\n\n## Database Access\n\n### Databases\n\n| Database | Path | Purpose |\n|----------|------|---------|\n| UploadThing | `\u002Fhome\u002Fgeorg\u002FOneDrive\u002Fuploadthing.db` | Repository grading |\n| Vacuum | `vacuum.db` (current directory) | Knowledge-check grading (must exist at start; error if missing) |\n\n### Schema\n\nTable: `users`\n\n| Column | Description |\n|--------|-------------|\n| `email` | Student email address |\n| `name` | Full student name |\n| `klasse` | Class identifier |\n\n### Lookup Protocol\n\n1. Match student by name (handle variations and partial matches).\n2. Normalize class comparison to uppercase: `WHERE UPPER(klasse) = UPPER(?)`.\n3. Retrieve `email` and `klasse` columns.\n4. Use `klasse` only for reporting; the address style comes from the\n   Anrede-Gate (`anrede.txt`), never from the class name.\n5. If `vacuum.db` is missing at start, stop immediately with error.\n6. Track all students with missing email addresses.\n\n## Missing Email Address Handling (CRITICAL)\n\nWhen generating `EMAIL.json`, if any student's email address cannot be found in\nthe database, the workflow MUST stop before generating the file.\n\n### Protocol\n\n1. **Collect all lookups**: Perform database lookup for all students first.\n2. **Check for missing emails**: After all lookups, identify students without\n   valid email addresses.\n3. **If any are missing**:\n   - STOP immediately.\n   - Do NOT generate `EMAIL.json`.\n   - Present ALL unresolved names to the user in a clear list:\n     ```\n     Die folgenden Schüler\u002Finnen konnten nicht in der Datenbank gefunden werden:\n     - [Name 1]\n     - [Name 2]\n     - ...\n     \n     Bitte aktualisieren Sie die Datenbank und starten Sie den Vorgang erneut.\n     ```\n   - Wait for user to update the database.\n   - User should confirm when database is updated.\n   - Retry the entire grading process.\n4. **Only proceed**: Generate `EMAIL.json` when ALL students have valid email\n   addresses.\n\n### Rationale\n\n- Prevents incomplete or invalid email payloads.\n- Ensures all students receive their feedback.\n- Forces database maintenance rather than workarounds.\n\n## Email JSON Structure\n\n```json\n[\n  {\n    \"mailto\": \"student@example.com\",\n    \"subject\": \"[Subject based on context]\",\n    \"body\": \"[Full personalized assessment in German]\"\n  }\n]\n```\n\n### Body Requirements\n\n- Language: German\n- Address student directly in second person (Sie or Du based on class)\n- Include full assessment content (long emails expected)\n- Preserve paragraph spacing and readable newline structure\n- Include greeting at start\n- Include closing formula at end\n- UTF-8 encoding with natural German umlauts (ä, ö, ü, ß)\n\n### JSON Escaping Rules (CRITICAL)\n\nThe `body` field often contains multi-paragraph German text with newlines, quotes,\nand special characters. **Improper escaping is the most common cause of broken\nemail JSON files.** The following rules MUST be followed:\n\n1. **All JSON string values must be valid JSON.** Newlines inside the `body`\n   field must use the escaped sequence `\\n` (two characters: backslash, `n`), NOT\n   literal line breaks. Double quotes must be escaped as `\\\"`, backslashes as `\\\\`,\n   and tabs as `\\t`.\n\n2. **No literal newlines inside JSON string values.** A file like this is INVALID:\n   ```json\n   \"body\": \"Sehr geehrte Frau Huber,\n\n   Sie haben die Abgabe gut gelöst.\"\n   ```\n   The correct form is:\n   ```json\n   \"body\": \"Sehr geehrte Frau Huber,\\n\\nSie haben die Abgabe gut gelöst.\"\n   ```\n\n3. **Validate before writing.** Every `*_email.json` file must parse successfully\n   with a standard JSON parser. After writing, verify with:\n   ```bash\n   python3 -c \"import json; json.load(open('FILE_email.json'))\"\n   ```\n\n4. **Detect double-escaped newlines.** After loading the JSON, check that the\n   `body` field does not contain literal `\\n` (backslash followed by n, which\n   indicates the file had `\\\\n` — double-escaped newlines):\n   ```bash\n   python3 \u003C\u003C 'PYEOF'\n   import json\n   d = json.load(open('FILE_email.json'))\n   body = d[0]['body']\n   assert '\\\\n' not in body, 'body contains literal backslash-n — double-escaped newlines'\n   print('OK')\n   PYEOF\n   ```\n   If validation fails, fix by reading the file with `json.load()`, replacing\n   literal `\\n` in the body with actual newlines, and re-writing with\n   `json.dump(..., ensure_ascii=False)`.\n\n5. **MUST use programmatic JSON writing.** You MUST use `json.dump()` or\n   `json.dumps()` to write `*_email.json` or `EMAIL.json`. Never construct JSON\n   via string interpolation, f-strings, or template languages. Always use\n   `ensure_ascii=False` to preserve UTF-8 umlauts (ä, ö, ü, ß). This guarantees\n   correct escaping of all special characters automatically.\n\n## Constraints\n\n- All grading content must be written in German.\n- All grading content must use second-person address (Sie or Du).\n- Never use third-person to refer to the student being graded.\n- UTF-8 encoding with natural German umlauts (ä, ö, ü, ß); never replace with\n  transliterations (ae, oe, ue) unless source material requires it.\n- Trailing comma required in all salutations.\n- Two newlines before signature line.\n- Three-space indentation before `Georg Graf`.\n- Never guess gender - use neutral fallback when uncertain.\n- Flag uncertain cases for manual review.\n- Grading must be performed through pure AI evaluation; never write scripts,\n  programs, or automated checkers to assess student work. For bulk grading,\n  use sub-agents instead (see `## Grading Methodology (CRITICAL)` above).\n\n## Repository Analysis Protocol\n\nShared pre-grading verification and analysis steps for all repository-based\ngrading skills (`repograde`, `projectgrade`).\n\n### Pre-Grading Verification\n\nBefore inspecting repository content, verify repository state:\n\n1. Navigate to the student repository directory.\n2. Run the Inner Git Repository Discovery to locate the actual Git working\n   directory inside the student directory.\n3. Run `git pull` inside the Git working directory to ensure latest version is\n   checked out.\n4. Run `git status` inside the Git working directory to check for uncommitted\n   changes.\n5. If uncommitted changes exist, STOP IMMEDIATELY and report to user.\n6. If pull fails or reports errors, STOP IMMEDIATELY and report to user.\n\n### Discovery\n\n- Identify relevant branches.\n- Enumerate commits while avoiding duplicate SHA processing.\n- Collect commit metadata: SHA, author, date, branch context, and message.\n\n### Per-Commit Inspection\n\nFor every relevant commit, inspect actual changes with commands equivalent to:\n\n- `git show --stat --summary \u003Csha>`\n- `git show --format=fuller --unified=3 \u003Csha>`\n\nUse diff content, not filenames alone, to identify what the student worked on.\n\n### Topic Detection\n\nDetect technical topics from the diffs and distinguish substantive work from\nsuperficial edits.\n\n- substantive: meaningful implementation, debugging, refactoring, feature work.\n- superficial: formatting-only edits, whitespace changes, trivial renames,\n  auto-generated files without meaningful modification.\n\nConsider common patterns across JavaScript, Java, C#, SQL, CSS, HTML, and\ngeneral programming constructs.\n\n### Branch and Activity Analysis\n\n- Focus primarily on `main`, but highlight significant non-main branch work.\n- Count commits over time.\n- Detect inactive gaps between first and last relevant commits.\n- Use evidence-based diligence signals such as `high`, `medium`, or `low`.\n\n## Repository Structure Discovery\n\nStudent repositories may use a two-level directory structure:\n\n```\nCWD\u002FSTUDENT_DIR\u002FINNER_PROJECT\u002F.git\n```\n\nThe `.git` directory (or `.git` file) is one level deeper than the student\ndirectory. This section defines how to locate the actual Git working directory\nbefore running any git commands.\n\n### Inner Git Repository Discovery\n\nFor each student repository, determine the correct Git working directory:\n\n1. **Enter the student directory** (or follow the symlink to its target).\n2. **Search for `.git`** inside the student directory up to 2 levels deep.\n3. The parent directory of the found `.git` is the **Git working directory**.\n4. All `git` commands (`pull`, `log`, `show`, `status`, `diff`) MUST be run\n   inside the Git working directory.\n5. Derive output filenames (`\u003Cbasename>_grading.md`, `\u003Cbasename>_email.json`)\n   from the **student directory basename** (the top-level name or the symlink\n   name), NOT from the inner project folder name.\n\n### Example\n\n```\n2ahwii-swp\u002Falw240094\u002FSWP-2025-26\u002F.git\n            ^^^^^^^^^ student dir basename (used for output filenames)\n                       ^^^^^^^^^^ Git working directory (where git commands run)\n```\n\n## Assignment Discovery Protocol (Aufgaben-Discovery)\n\nShared assignment source discovery for `repograde` and `projectgrade`.\nAssignments (Aufgaben) may exist in multiple locations. The grading skill MUST\ndiscover and merge all sources into a unified assignment list before matching\nagainst student commits.\n\n### Terminology (CRITICAL)\n\n- **Abgabe** is the canonical term for the graded unit in ALL output: emails,\n  `*_grading.md`, `GRADINGS.md`, `CLASS.md`, scoring tables and the Noten-TSV.\n  User-visible content spells it out (\"Abgabe 1\", \"Abgabe 2\") with a sequential\n  number — never \"Aufgabe\", \"Übung\", \"Hausübung\", \"HÜ\", or \"homework\".\n- **Aufgabe** (plural: **Aufgaben**) survives only as the name of the task\n  document\u002Fartifact: `Aufgabe.md`, the `## Aufgaben` index in the cohort README,\n  and the assignment-discovery sources below. It is the task that was given,\n  not the submission that is graded.\n- **Übung** is neither: Übungen (e.g. \"Jetzt du!\") are in-class\n  Mitarbeits-Elemente and MUST never appear in the assignment list.\n- The word **Hausübung** is legacy vocabulary. It is recognized only inside\n  legacy sources (see below) and never used in new output.\n\n### Source 1: Cohort README (via `_class` Symlink, Preferred)\n\nCheck for a `_class` symlink in the CWD. If it exists, follow it and read the\ncohort README (`README.md`) inside the linked class folder.\n\n#### 1a: Canonical Assignment List\n\nLook for a `## Aufgaben` section near the top of the cohort README. It is a\nspare bullet-list index with one line per assignment:\n\n```markdown\n## Aufgaben\n\n- 2026-09-29: Seite dekorieren (CSS) → `2026-10_XX_css-basics\u002F`\n```\n\nParse each bullet as `(iso_date, short_description, optional_relative_link)`:\n\n- A relative link on the bullet is a **Markdown link** whose target is the\n  lesson folder path relative to the cohort README directory, e.g. the bullet\n  above may equivalently carry `[2026-10_XX_css-basics\u002F](2026-10_XX_css-basics\u002F)`;\n  the link target is a lesson folder (Tagesordner) whose assignment content is\n  read from there (see Source 2). The list is an INDEX only — the task text\n  lives in the lesson folder, never in the list itself.\n- Entries without a link are matched by date\u002Ftopic against the other sources.\n- Grading is read-only: if the canonical list is missing, do NOT create it.\n  Report its absence as a note.\n\n#### 1b: Dated Sections in the Cohort README\n\nSections with `## YYYY-MM-DD` headings may contain assignment text directly.\nExtract the date from the heading and the assignment content from the section\nbody. Relative deadlines (\"bis nächste Woche 00:00\") are resolved via the wu\ntermine (see Deadline Calculation).\n\n### Source 2: Per-Lesson Folders inside `_class`\n\nScan `_class\u002F` for subdirectories matching `\u003CYYYY-MM-DD>_\u003Ctopic>`,\n`\u003CYYYY-MM-DD>__\u003Ctopic>` (create-lesson convention, double underscore), or an\nambiguous `\u003CYYYY-MM>_\u003CX>_\u003Ctopic>` form (e.g. `2026-10_XX_css-basics`) whose\ndate is resolved via Git (see Git Folder Provenance Resolution below).\n\nFor each matching directory, check for assignment content in this order:\n\n1. `Aufgabe.md` — dedicated assignment file (canonical)\n2. `README.md` — Tages-README with an assignment section under the heading\n   `## Aufgabe` or `## ? Aufgabe`\n3. Legacy files (data from earlier school years only): `Hausübung.md`,\n   `Angabe_HÜ.md`, or a README section under `## Hausübung` \u002F `## ? Hausübung`.\n   If the legacy section states \"Keine Hausübung\", skip this lesson.\n\nWhen a file is found:\n- Extract the date from the directory name — a full `YYYY-MM-DD` is the\n  CANDIDATE assignment date, subject to the Git Folder Provenance check below\n  (a renamed folder keeps the date of its OLDEST name).\n- Extract the topic from the directory name and\u002For the file content.\n- Read the file content for assignment details.\n- Add to the assignment list: `(iso_date, topic, content)`.\n\n#### Reference Pattern (Indirection to a Prepared Lesson)\n\nThe `## Aufgabe` section of a Tages-README may be a REFERENCE rather than task\ntext, for example: \"Wir haben Lesson `KM3-03-css-basics` gemacht, die Aufgabe\nist die Aufgabe von dort.\" Detection: a lesson reference matching\n`\u003CPREFIX>-\u003CNN>-\u003Cslug>` and\u002For a relative link into `unterricht\u002F`.\n\nResolution:\n\n1. Resolve the lesson folder relative to the teaching-repo root:\n   `\u003Crepo-root>\u002Funterricht\u002F\u003CPREFIX>-\u003CNN>-\u003Cslug>\u002F` (from a cohort folder one\n   level below the repo root this is `..\u002Funterricht\u002F…`, from two levels\n   `..\u002F..\u002Funterricht\u002F…`).\n2. Read the assignment content from that folder in this order: `aufgabe.md`,\n   `hausaufgabe.md` (legacy), the Tages-README template\n   (`\u003CPREFIX>-\u003CNN>-\u003Cslug>.md`), or the `## Aufgabe` section of the lesson.\n3. Assignment date and deadline stay cohort-side (folder name \u002F Git \u002F wu);\n   only the CONTENT is resolved remotely. An explicit deadline found in the\n   referenced content still wins (see Deadline Calculation).\n\n### Source 3: Legacy Cumulative File\n\nCheck for a `Hausübungen.md` file directly in the CWD or inside `_class\u002F`\n(may be a symbolic link; follow symlinks when reading). This is the legacy\nsingle-file approach from earlier school years. If found, parse it using the\nSemantic Date Extraction rules below.\n\n#### Semantic Date Extraction\n\n**You MUST perform a thorough semantic analysis of the entire file.**\n\nMany agents fail at this step because they:\n- Only read the first entry\n- Miss dates embedded in the text (not just headings)\n- Fail to convert German date formats to ISO\n- Skip entries that don't match a naive pattern match\n\nRequired steps:\n\n1. **Read the ENTIRE file** — do not stop after the first assignment entry.\n2. **Extract ALL date references** from:\n   - Headings: `## Hausübung vom 18. Februar`\n   - Inline dates: `Abgabe bis 25. Februar`\n   - Date ranges: `Zeitraum: 10.-18. März`\n   - Relative dates: `nächste Woche`, `in 2 Wochen` (convert to absolute)\n3. **Normalize ALL dates to ISO format** (YYYY-MM-DD).\n4. **Build an assignment list**: `[(iso_date, topic, content), ...]`.\n\nDate patterns to recognize:\n\n| Pattern | Example | Extraction |\n|---------|---------|------------|\n| `vom DD. Monat` | `vom 18. Februar` | 2026-02-18 (infer year) |\n| `vom DD. Monat YYYY` | `vom 18. Februar 2026` | 2026-02-18 |\n| `DD.MM.YYYY` | `18.02.2026` | 2026-02-18 |\n| `bis DD. Monat` | `Abgabe bis 25. Februar` | 2026-02-25 (deadline) |\n| `Zeitraum: DD.-DD. Monat` | `Zeitraum: 10.-18. März` | 2026-03-10 to 2026-03-18 |\n\nMonth name mapping:\n\n```\nJanuar = 01    Juli = 07\nFebruar = 02   August = 08\nMärz = 03      September = 09\nApril = 04     Oktober = 10\nMai = 05       November = 11\nJuni = 06      Dezember = 12\n```\n\nWhen year is not explicit, infer from the grading context or cross-reference\nwith commit dates.\n\n**Common Failure Pattern (AVOID THIS):**\n```\n❌ WRONG: Read Hausübungen.md, see first entry is \"vom 18. Februar\",\n         requested date is 2026-02-10, conclude \"no matching assignment\"\n✅ CORRECT: Parse ALL entries, find \"vom 10. Februar\" (matches),\n            \"vom 18. Februar\" (after cutoff), \"vom 25. Februar\" (after cutoff)\n```\n\n### Source 4: Per-Lesson Files in CWD\n\nScan the CWD itself for subdirectories matching `\u003CYYYY-MM-DD>_\u003Ctopic>` that\ncontain assignment content (same file search order as Source 2, including the\nReference Pattern). Apply Future Lesson Exclusion identically.\n\n### Git Folder Provenance Resolution\n\nA lesson folder's NAME is not a reliable assignment date: folders may be\nRENAMED forward (e.g. a folder created as `2026-09-22_html-basics` renamed to\n`2026-09-29_html-basics` when the next UE approached). The assignment date\nMUST therefore be resolved from the folder's Git provenance.\n\n**Pitfall (CRITICAL):** Never trust `git log --diff-filter=A` on the current\npath alone — renamed files register as `A` at the new path. Always use rename\ndetection (`-M` \u002F `--follow`).\n\nResolution (first match wins):\n\n1. **Reconstruct the name history** (run inside the `_class` target repo):\n   ```bash\n   git log -M --name-status --date=iso --format='%h %ad' --diff-filter=AR \\\n       -- \u003Cklasse>\u002F\u003Ccurrent-folder>*\n   ```\n2. **Rename found**: the name history is `oldest-name → … → current-name`.\n   The assignment date is the date encoded in the OLDEST name of that chain\n   (e.g. `2026-09-22_html-basics` → assignment date 2026-09-22). The rename\n   commit date itself is NEVER the assignment date.\n3. **No rename, full `YYYY-MM-DD` in the current name**: that date is the\n   assignment date.\n4. **No rename, ambiguous name** (e.g. `2026-10_XX_css-basics`): the\n   assignment date is the DATE PART of the oldest commit that added files in\n   the folder.\n5. **wu validation (all cases)**: the resolved date must be a real lesson\n   date from the wu termine; snap if needed (latest lesson date ≤ resolved\n   date). If both the current name date and the provenance date are real\n   lesson dates, the provenance date (oldest name) wins.\n6. **README cross-check**: a lesson date declared in the Tages-README (e.g.\n   \"HTML-Grundlagen (2026-09-22)\") is a cross-check signal. On conflict, the\n   provenance\u002Fwu date wins and the conflict MUST be reported to the user —\n   never silently resolved.\n7. **Same-day renames (preparation case)**: when the folder was renamed on\n   the SAME day it was created (e.g. created as `2026-09-29_css-basics` and\n   renamed in the same commit to `2026-10_XX_css-basics`), the folder was\n   prepared during that UE for a LATER UE — the oldest-name date is the\n   preparation date, NOT the assignment date. In that case the assignment\n   date comes from a dated cohort README section documenting the UE (e.g.\n   `## 2026-10-06__css`) or from the wu termine, and the ambiguity MUST be\n   reported. Note: lesson material may be DELETED after being moved into\n   `unterricht\u002F` — the cohort README reference section remains the\n   assignment source (Reference Pattern applies).\n\n### wu Termine Integration (MANDATORY)\n\nDerive the lesson address from the `_class` symlink target\n`…\u002FGRG-\u003CFACH>\u002F\u003Cklasse>\u002F` as `wu lesson \u003Cklasse>\u002F\u003Cfach>` (e.g.\n`_class -> GRG-WMC\u002F3aaif` → `wu lesson 3aaif\u002Fwmc`). Query the termine:\n\n```bash\nwu lesson 3aaif\u002Fwmc termine --json\n```\n\nThe termine list is used to:\n\n1. **Snap dates**: map git commit dates to real lesson dates (latest lesson\n   date ≤ commit date).\n2. **Compute deadlines**: deadline = next DISTINCT lesson date strictly after\n   the assignment date, at 00:00 (duplicate slots on one date count once;\n   holidays are respected automatically).\n3. **Resolve relative deadlines** (\"bis nächste Woche\", \"bis zur nächsten UE\").\n\n**Failure rule (CRITICAL):** If `wu` is not installed, fails, or returns no\ntermine, STOP IMMEDIATELY with a German error message (e.g. \"wu liefert keine\nTermine — Aufgabendaten und Deadlines können nicht aufgelöst werden.\"). Never\nguess dates or deadlines.\n\n### Future Lesson Exclusion\n\nPer-lesson directories with a date in the future represent prepared but\nnot-yet-held lessons. Their assignments MUST NOT be included in the unified\nassignment list.\n\n1. Determine today's date: `date +%Y-%m-%d`\n2. For each per-lesson directory, resolve the assignment date via Git Folder\n   Provenance Resolution.\n3. If `lesson_date > today`, skip this directory entirely.\n4. Do NOT include its assignment, regardless of whether an assignment section\n   exists in the file.\n\n### Merging All Sources\n\n1. Collect assignment entries from all sources into a single unified list.\n2. If multiple sources describe the same assignment, prefer the per-lesson\n   file content over the canonical list index and over the legacy cumulative\n   file.\n3. Sort the unified list by date.\n4. If no source provides any assignment entries, report this to the user and\n   grade based on available work only.\n\n## Aufgaben-Ablage-Ordnung (Order to Keep When Giving Assignments)\n\nThis section documents the expected repository layout. Keep to it when\ncreating or preparing assignments:\n\n1. **Kohorten-README** (`\u003Cklasse>\u002FREADME.md`): carry a spare canonical\n   `## Aufgaben` bullet list near the top — one line per assignment with ISO\n   date, a few words, and a relative link to the Tagesordner. The list is an\n   index only; the task text lives in the lesson folder.\n2. **Tagesordner** (`\u003Cklasse>\u002FYYYY-MM-DD_\u003Ctopic>\u002F`; the create-lesson double\n   underscore `YYYY-MM-DD__\u003Ctopic>` is also accepted): contains `Aufgabe.md`\n   or a `README.md` with an `## Aufgabe` section — full task text or a\n   reference to a Prepared Lesson (`unterricht\u002F\u003CPREFIX>-\u003CNN>-\u003Cslug>\u002F`).\n3. Ambiguous folder names like `YYYY-MM_XX_\u003Ctopic>` are allowed; the date is\n   resolved via the teaching repo's Git history and snapped to a wu lesson\n   date. Renaming a lesson folder forward is also fine — the discovery\n   attributes the date of the OLDEST name in the folder's Git history and\n   reports the rename.\n4. **Deadline**: state a concrete date (\"Abgabe per Push bis 2026-10-13\n   00:00\") or \"bis zur nächsten UE\" (resolved via wu). Vague formulations\n   that wu cannot resolve are not acceptable.\n5. Prepared but not-yet-held lessons (future dates) may already exist; they\n   are excluded automatically until the date arrives.\n6. Assignments NEVER go into a student repository.\n7. The word \"Übung\" marks in-class exercises, never assignments. Do not use\n   \"Hausübung\" for new content.\n\n## Deadline Calculation\n\n### Determining the Assignment Date\n\n1. Full `YYYY-MM-DD` in the lesson folder name → that date is the assignment\n   date.\n2. Ambiguous or renamed folder name (e.g. `2026-10_XX_css-basics`,\n   `2026-09-29_html-basics` renamed from `2026-09-22_html-basics`) → Git\n   Folder Provenance Resolution, snapped via wu to a real lesson date.\n3. Cohort README dated section → the heading date.\n\nThe assignment date MUST correspond to an actual lesson date from\n`wu lesson \u003Cklasse>\u002F\u003Cfach> termine --json`.\n\n### Determining the Deadline\n\nPrecedence:\n\n1. **Explicit deadline in the assignment text** (e.g. \"Abgabe per Push bis\n   2026-09-29 00:00\") ALWAYS wins. Parse ISO (`YYYY-MM-DD`) and German dates.\n2. **Otherwise**: deadline = next DISTINCT lesson date strictly after the\n   assignment date, at 00:00:00, taken from the wu termine. Duplicate slots\n   on the same date count once; this automatically respects holidays\n   (e.g. assignment 2026-10-13 → deadline 2026-10-20T00:00:00 when 10-13 is\n   followed by 10-20).\n3. **Relative deadlines** (\"bis nächste Woche\", \"bis zur nächsten UE\") are\n   resolved to the next lesson date at 00:00 via the wu termine.\n\nSubagents MUST take the deadline from the wu output (or an explicit deadline\nin the assignment text) and MUST use shell date arithmetic only for\nformatting, never for guessing:\n\n```bash\nwu lesson 3aaif\u002Fwmc termine --json\n```\n\n**Failure rule:** If wu is unavailable or returns no termine, STOP IMMEDIATELY\nwith a German error message. Never fall back to a fixed \"+7 days\" guess.\n\n### Commit Filtering by Deadline\n\n```bash\n# On-time commits (assignment ≤ commit ≤ deadline):\ngit log --after=\"2026-02-10\" --before=\"$DEADLINE\" --oneline\n\n# Late commits (commit after deadline):\ngit log --after=\"$DEADLINE\" --oneline\n```\n\n### Punctuality Classification\n\n| Latest relevant commit | Classification | Factor | Meaning |\n|------------------------|----------------|--------|---------|\n| ≤ DEADLINE             | pünktlich      | 1.0    | Full points possible |\n| > DEADLINE             | verspätet      | 0.75   | 25% penalty on achieved quality, regardless of how late |\n| No commits             | fehlend        | —      | 0 points for this assignment |\n\n### Deadline in Reports\n\nReports MUST mention the concrete deadline for each assignment, including the\nexact `YYYY-MM-DDT00:00:00` timestamp and whether the student's latest\nrelevant commit fell before or after it.\n\n## Bulk Grading Protocol\n\nShared concurrency and directory handling for `repograde` bulk mode.\n\n### Directory Exclusion\n\nWhen enumerating student repositories, **exclude**:\n- `_class` (assignment symlink)\n- Any directory starting with `_` (underscore prefix)\n- Any directory starting with `.` (dot prefix)\n- Any regular files (not directories and not symlinks)\n\nThese excluded entries are NOT student repositories and must never be graded.\n\n### Symlink Handling\n\nStudent repositories may be accompanied by name-based symlinks:\n\n```\nAlwazeh Sami -> alw240094\n```\n\nThese symlinks are aliases to student directories. They serve for name\nresolution (see `Student Name Resolution via Symlinks`) but are NOT\nseparate repositories. Only the target directories are graded.\n\nWhen enumerating:\n1. If a symlink points to another directory inside the CWD, skip it as a\n   candidate for grading — only the target directory is graded.\n2. Use the symlink's name for student identification.\n3. Follow the symlink to find the actual repository for git operations.\n4. If the symlink is BROKEN (target does not exist), it is NOT skipped and\n   NOT an error — see Broken Name-Symlinks (Missing Repository) below.\n\n### Concurrency\n\n- Default maximum: 5 repositories in progress at once.\n- When one grading run completes, start the next after an approximately\n  3-second delay.\n- Each subagent must derive its output stem from the repository basename and\n  write only `\u003Cbasename>_grading.md` plus `\u003Cbasename>_email.json`.\n- Subagents must never write shared `EMAIL.json`.\n\n### Aggregation\n\nAfter all subagents finish, the master workflow aggregates per-student\n`*_email.json` files into a single `EMAIL.json`.\n\n**Stop condition:** Verify the aggregation script exists:\n```bash\nSCRIPT=~\u002F.opencode\u002Fskills\u002Fgrading-shared\u002Faggregate-emails.py\nif [ ! -f \"$SCRIPT\" ]; then\n    echo \"ERROR: $SCRIPT not found\" >&2\n    exit 1\nfi\n```\nIf the script is missing, **STOP**, report the path to the user, and wait\nfor resolution before continuing.\n\n**Execution:** Run the script from the current working directory:\n```bash\n~\u002F.opencode\u002Fskills\u002Fgrading-shared\u002Faggregate-emails.py\n```\nIf the script exits with a non-zero status or reports errors on stderr,\n**STOP**, display its output to the user, and do not proceed.\n\n## Noten-TSV für den Google-Doc-Import (MANDATORY)\n\nJeder Lauf, der `EMAIL.json` erzeugt, erzeugt zusätzlich eine **Noten-TSV**, die\nper Copy-Paste in die Noten-Tabelle des Lehrers übernommen wird\n(`repograde` und `knowledge-assessment`).\n\n### Ablage und Umfang\n\n- Dateiname: `noten.tsv` im **Lauf-Ordner** des privaten `-T`-Repos:\n  `\u003Csubject>-T\u002F\u003Ckohorte>\u002F\u003CYYYY-MM-DD>_\u003Cslug>\u002Fnoten.tsv`\n  (Lauf-Ordner bei Bedarf anlegen, siehe Zielordner-Regel der Skills).\n- **Pro Lauf eine völlig neue Datei.** Keine Fortschreibung, keine\n  Zustandsdatei: die TSV enthält immer nur die Spalten dieses Laufs.\n\n### Layout\n\n- Spalte **A** = Vollname (Nachname Vorname, exakt wie `wu … roster` und die\n  `-T`-Dateinamen)\n- Spalte **B** = leer (Trennspalte)\n- ab Spalte **C** je Abgabe (Hausübung) bzw. je Knowledge-Check eine Spalte\n\n| Zeile | Inhalt |\n|-------|--------|\n| 1 | `Abgabe \u003Cvon>–\u003Cbis>` (z. B. `Abgabe 06.10.–13.10.2026`); bei WÜ `WÜ \u003CISO-Datum>` |\n| 2 | maximal erreichbare Punkte (100 je Abgabe; bei WÜ die Gesamtpunkte der Lösungsdatei) |\n| 3 | leer |\n| 4 ff. | Punkte je Schüler:in, ganzzahlig kaufmännisch gerundet, fehlend = `0` |\n\nBeispiel (Hausübungen):\n\n```\n\t\tAbgabe 06.10.–13.10.2026\tAbgabe 13.10.–20.10.2026\n\t\t100\t100\n\t\t\t\nAbazi Hamza\t\t80\t53\nAhmad Ali\t\t0\t0\n```\n\n### Datenquelle: Punkte-Sidecars\n\nJeder Subagent schreibt zusätzlich zu `_grading.md` und `_email.json` einen\nSidecar `\u003Cbasename>_points.json` (bei `repograde` der Repo-Basename, bei\n`knowledge-assessment` der Schüler:innen-Name; JSON-Array mit genau einem\nObjekt, geschrieben mit `json.dump(..., ensure_ascii=False)`):\n\nHausübung (`repograde`):\n\n```json\n[{\n  \"name\": \"Abazi Hamza\",\n  \"kind\": \"homework\",\n  \"klasse\": \"3AAIF\",\n  \"assignments\": [\n    {\"from\": \"2026-10-06\", \"to\": \"2026-10-13\",\n     \"label\": \"Tooling & Git\",\n     \"title\": \"Aufgabe 1: Tooling & Git (bis 13.10.2026)\",\n     \"max\": 100, \"points\": 80}\n  ]\n}]\n```\n\n`points` ist der Effective-Wert der Abgabe (`Quality × Factor`, also z. B.\n`52.5`), `max` ist `100` je Abgabe.\n\n`assignments` enthält **genau einen Eintrag je Aufgabe** des Laufs, nicht einen\nSammelwert über den ganzen Zeitraum. `title` ist optional und bestimmt den Text\nin Zeile 1 der TSV; er ist Pflicht, sobald zwei Aufgaben denselben Zeitraum\nhaben (gleiche Deadline), weil die Spalten sonst verschmelzen. Ohne `title`\nbleibt es beim automatischen `Abgabe \u003Cvon>–\u003Cbis>`.\n\nWird dieselbe Aufgabe mehrfach gestellt (etwa mit verschobenem Termin), ist sie\n**eine** Aufgabe: ein Eintrag, und es gilt der **spätere** Abgabetermin. Zwei\nSchreibweisen desselben Zeitpunkts (z. B. `2026-09-23` 23:59 und\n`2026-09-24T00:00:00`) sind kein zweiter Termin — dort gilt die Schreibweise,\ndie der Unterrichts-\u002FAngabepraxis entspricht.\n\nKnowledge-Check (`knowledge-assessment`):\n\n```json\n[{\n  \"name\": \"Abazi Hamza\",\n  \"kind\": \"knowledge\",\n  \"klasse\": \"3AAIF\",\n  \"exam\": \"2026-10-20\",\n  \"max\": 145,\n  \"points\": 88\n}]\n```\n\n`max` stammt aus den Gesamtpunkten der Lösungsdatei\n(`knowledge_\u003Cklasse>_\u003Cdatum>_solutions.md`), nie aus einer Annahme.\n\nDer Sidecar ist reine Ausgabeformatierung — die Bewertung selbst bleibt\nausschließlich KI-Beurteilung.\n\n### Erzeugung: Probelauf zuerst (CRITICAL)\n\n```bash\n# 1. Roster beschaffen (TSV: Name\u003CTAB>Klasse)\nwu lesson \u003CKLASSE\u002FFACH> roster > roster.tsv\n\n# 2. Probelauf — read-only, Tabelle geht nach stdout\n~\u002F.opencode\u002Fskills\u002Fgrading-shared\u002Femit-grade-tsv.py --roster roster.tsv\n\n# 3. Erst nach Sichtung der Tabelle wirklich schreiben\n~\u002F.opencode\u002Fskills\u002Fgrading-shared\u002Femit-grade-tsv.py --roster roster.tsv --write\n```\n\nRegeln:\n\n- Das Skript ist **ohne `--write` immer read-only**; der Probelauf ist Pflicht\n  und wird dem User gezeigt.\n- Zeilen kommen aus dem **Roster** (`wu lesson \u003CKLASSE\u002FFACH> roster`), nicht aus\n  den Abgaben — fehlende Abgaben erscheinen als `0`. Fällt `wu` aus: STOPP mit\n  deutscher Meldung (kein Raten, keine Alternativliste).\n- Namen werden normalisiert abgeglichen (`_` → Leerzeichen, Groß-\u002FKleinschreibung\n  egal). Abgaben ohne Roster-Eintrag werden angehängt **und** als Warnung\n  gemeldet; Roster-Einträge ohne Abgabe erscheinen mit `0`.\n- Widersprüchliche Maximalpunkte, gemischte Arten (Hausübung + Knowledge-Check)\n  oder fehlende Sidecars brechen mit Exitcode 1 ab.\n- Nach `--write` wird `noten.tsv` gemeinsam mit den übrigen Ausgaben im\n  `-T`-Repo committet.\n\n### Nachträgliche TSV für Läufe ohne Sidecars\n\nLäufe, die vor Einführung der Noten-TSV (2026-11-01) entstanden sind, haben\n`GRADINGS.md` und `EMAIL.json`, aber weder `*_points.json` noch `noten.tsv`.\nDiese Lücke wird nachgezogen, ohne die Bewertung neu zu machen:\n\n1. **Knowledge-Checks:** Punkte aus der Übersichtstabelle der `GRADINGS.md`\n   (Spalte „Gesamt\") — dort gibt es nur eine Spalte je Lauf.\n2. **Repo-Läufe:** Punkte **je Aufgabe** aus dem Abschnitt `## Endbewertung`\n   des jeweiligen `\u003Cname>_grading.md` (`Quality × Factor`, `max = 100`). Die\n   Übersichtstabelle der `GRADINGS.md` taugt dafür nicht — sie enthält nur die\n   aggregierte Endbewertung. Ein Lauf mit N Aufgaben ergibt N Spalten.\n3. Sidecars **außerhalb** des Repos erzeugen (z. B. unter `\u002Ftmp\u002Fopencode\u002F`) und\n   dort `emit-grade-tsv.py --out \u003Clauf>\u002Fnoten.tsv --write` aufrufen.\n   Die Sidecars sind hier reine Zwischenprodukte und werden **nicht**\n   committet; committet wird ausschließlich `noten.tsv`.\n4. Spaltenkopf je Aufgabe: `title` = `Aufgabe N: \u003CThema> (bis DD.MM.YYYY)`.\n   Thema und Deadline aus den Einzelgutachten (Mehrheitsschreibweise). Keine\n   `von`-Daten erfinden — die Abgabezeiträume sind oft nicht belegbar.\n5. **Namensabweichungen vor dem Lauf explizit mappen.** Gutachten führen oft\n   Kurzformen (`Moza Lukas`, `Figueroa Denise`), der Roster den vollen\n   WebUntis-Namen (`Moza Lukas, Kevin`, `Figueroa Castillo Karla Denise`).\n   Ohne Mapping erscheint die Person doppelt: einmal mit `0` aus dem Roster,\n   einmal angehängt mit ihren Punkten.\n6. **Verifikation:** die Summe der Einzelwerte muss die Zeile „Erreichte\n   Punkte\" des Gutachtens ergeben. Weicht sie ab, ist die **Quelldatei**\n   fehlerhaft — nicht die TSV. Solche Fälle dokumentieren und dem User\n   vorlegen, statt die Differenz einer Aufgabe zuzuschlagen.\n\n## Email Body Format (CRITICAL)\n\n\nAll grading email bodies MUST be clear ASCII text. This section defines the\nexact structure and formatting rules for every email body generated by any\ngrading skill.\n\n### Plain-Text Rule\n\nEmail bodies are plain ASCII text with **one exception**: code blocks with\nbacktick fences (e.g., ```sql ... ```) for short source-code snippets that\nhelp illustrate a grading point.\n\n**Forbidden in email bodies:**\n- Markdown headers (`##`, `###`, etc.)\n- Bold text (`**...**`)\n- Italic text (`*...*`)\n- Markdown tables\n- Markdown bullet lists (`- item` or `* item`)\n- Horizontal rules (`---`)\n- Any other Markdown formatting\n\n**Allowed in email bodies:**\n- Plain text paragraphs separated by blank lines.\n- Code blocks with backtick fences for short source-code snippets.\n- Natural German text with UTF-8 umlauts (ä, ö, ü, ß).\n\n### Structure for Assignment Grading Emails\n\nUsed by `repograde` skill (single-repo and bulk mode). The email body follows\nthis exact paragraph order:\n\n1. **Greeting** — per Greeting Formulas section above.\n\n2. **Automatic assessment disclaimer** — per Automatic Assessment Disclaimers section above.\n\n3. **Opening sentence** (address style dependent):\n   - Formal: `Ich habe Ihre Abgaben, welche im Zeitraum vom [Start-Datum] bis zum [End-Datum] aufgegeben waren, durchgesehen.`\n   - Informal: `Ich habe deine Abgaben, welche im Zeitraum vom [Start-Datum] bis zum [End-Datum] aufgegeben waren, durchgesehen.`\n   - Dates in German long format (e.g., \"18. Februar 2026\", \"4. März 2026\").\n\n3. **Commit summary** — Two to three sentences summarizing the commits\n   discovered in the student's repository during the grading period.\n   Mention overall activity, approximate number of substantive commits,\n   and any notable patterns (gaps, bursts, consistent work).\n\n4. **Assignment overview** — A concise list of all Abgaben,\n   each in one to two sentences. Format:\n\n   ```\n   Abgabe 1: [Topic\u002Fcontent summary in 1-2 sentences]\n   Abgabe 2: [Topic\u002Fcontent summary in 1-2 sentences]\n   ```\n\n   Use \"Abgabe\" spelled out with a sequential number. Never abbreviate\n   (no \"HÜ\", no \"A1\").\n\n5. **Per-assignment evaluation** — For each Abgabe, provide\n   a detailed discussion and evaluation on a 0–100% scale. Each evaluation\n   is a separate paragraph covering:\n   - What the student did well.\n   - What was missing or could be improved.\n   - Specific technical observations (with code snippets where helpful).\n   - A percentage rating for that individual assignment.\n\n6. **Coverage \u002F timeliness summary** — One short paragraph summarizing overall\n   Abgaben coverage and whether submissions were on time, late, partial, or\n   missing where relevant.\n\n7. **Recommendations and praise** — A warm, genuine paragraph with\n   improvement suggestions and subtle praise. See Praise Guidelines below.\n\n8. **Final score summary** — One short paragraph containing the final weighted\n   result in the form `Endbewertung: XX\u002F100 (XX%)`.\n\n9. **Closing** — per Closing Formulas section above.\n\nKeep email length reasonably consistent across students. Do not omit one of the\nparagraph groups above simply because a repository is weaker or stronger; if\nevidence is limited, say so briefly and continue with the same structure.\n\nFor students WITHOUT a repository (broken name-symlink), see the\nMissing Repository Emphasis below — that variant replaces paragraph groups\n3-6.\n\n### Missing Repository Emphasis (No-Repo Cases)\n\nFor a student whose repository is missing (broken name-symlink — see Broken\nName-Symlinks), the email body consists of: greeting, automatic assessment\ndisclaimer, then the emphasis paragraph as the FIRST content paragraph\n(adapted to the address style), a short consequences paragraph, and the\nclosing. The emphasis paragraph wording:\n\n- Informal: `Zu deinem Aufgaben-Repository liegt mir bislang kein Link vor. Das ist der mit Abstand wichtigste offene Punkt: Ohne die Bekanntgabe deines Repositories ist keine positive Beurteilung möglich. Sende mir bitte UMGEHEND den URL deines GitHub-Repositories an grafg@spengergasse.at — genau so haben wir es in der ersten UE vereinbart. Sobald das Repository verfügbar ist, werde ich alle Abgaben nachbewerten.`\n- Formal: `Zu Ihrem Aufgaben-Repository liegt mir bislang kein Link vor. Das ist der mit Abstand wichtigste offene Punkt: Ohne die Bekanntgabe Ihres Repositories ist keine positive Beurteilung möglich. Senden Sie mir bitte UMGEHEND den URL Ihres GitHub-Repositories an grafg@spengergasse.at — genau so haben wir es in der ersten UE vereinbart. Sobald das Repository verfügbar ist, werde ich alle Abgaben nachbewerten.`\n\nFollow with one short paragraph stating that all Abgaben in the grading\nperiod currently count 0% and that the final result is\n`Endbewertung: 0\u002F100 (0%)`.\n\n### Structure for Knowledge-Check Emails\n\nUsed by `knowledge-assessment` skill. Same plain-text rule applies.\n\n1. **Greeting** — per Greeting Formulas section above.\n\n2. **Automatic assessment disclaimer** — per Automatic Assessment Disclaimers section above.\n\n3. **Opening sentence** (address style dependent):\n   - Formal: `Ich habe Ihre Wissensüberprüfung vom [Datum] durchgesehen.`\n   - Informal: `Ich habe deine Wissensüberprüfung vom [Datum] durchgesehen.`\n\n4. **Score summary** — Total points achieved out of total possible points.\n\n4. **Question-by-question analysis** — Plain text paragraphs discussing\n   strengths and weaknesses in specific questions.\n\n5. **Recommendations and praise** — Same guidelines as assignment emails.\n\n6. **Solutions note** — `Ich habe die Datei mit den korrekten Lösungen in das Git-Repository hochgeladen.`\n\n7. **Closing** — per Closing Formulas section above.\n\n### Praise Guidelines\n\nPraise must be **subtle and understated**. Never effusive, never over the top.\n\n**Good praise examples:**\n- \"Die JOIN-Operationen haben Sie sauber umgesetzt.\"\n- \"Hier ist Ihnen eine klare Struktur gelungen.\"\n- \"Das zeigt ein gutes Verständnis der Konzepte.\"\n- \"An dieser Stelle war Ihre Lösung besonders durchdacht.\"\n\n**Bad praise examples (DO NOT USE):**\n- \"Hervorragende Arbeit! Brillant gelöst!\"\n- \"Sie sind ein wahrer Meister der Programmierung!\"\n- \"Phantastisch! Weiter so!\"\n- \"Ich bin beeindruckt von Ihrer überragenden Leistung!\"\n\nThe tone should feel like a calm, observant teacher noting genuine strengths\nwithout exaggeration. Let the facts speak for themselves. When a student did\nsomething well, state it matter-of-factly.\n\n### Examples\n\nWorked formal and informal assignment email bodies are in\n[EMAIL-EXAMPLES.md](.\u002FEMAIL-EXAMPLES.md). Use them as shape references, not as\ntext to copy verbatim.\n\n## Reporting Protocol\n\nAll grading content must be written in German and address the student directly\nin the second person (Sie or Du based on class). See the Second-Person Address\nsection above for pronoun usage and examples.\n\nThe `*_grading.md` files may use full Markdown formatting (headers, bold,\ntables, lists). Only the email body in `*_email.json` is restricted to plain\ntext per the Email Body Format section above.\n\nReports should include, where applicable:\n\n- repository overview\n- assignment-by-assignment summary\n- topic coverage\n- per-commit technical analysis\n- non-main branch activity\n- activity over time\n- inactive gaps\n- diligence assessment\n- final evaluation with `Endbewertung: XX\u002F100`\n\nWhen a consuming skill defines an exact mandatory report template, that template\noverrides any looser interpretation of this Reporting Protocol. In such cases,\nagents and subagents MUST follow the exact heading names, order, and section\nscope defined by the consuming skill and MUST NOT improvise alternative report\nstructures.\n\n## Student Name Resolution via Symlinks\n\nWhen the CWD contains name-based symlinks (`Lastname Firstname -> STUDENT_ID`),\nuse the symlink name to identify the student.\n\n### Resolution Protocol\n\n1. **Detect symlinks**: For each entry in the CWD, check if it is a symlink\n   (`[ -L \"$entry\" ]`) and if it points to another student directory within\n   the CWD.\n2. **Parse the symlink name**: Apply the standard Name Parsing from Repository\n   Basename rule (last word = first name, everything before = last name).\n3. **Resolve student identity**:\n   - For grading reports and `GRADINGS.md`: use the symlink name\n     (e.g., \"Alwazeh Sami\").\n   - For database lookup (email): use the symlink name.\n   - For git operations: follow the symlink to the target directory, then\n     apply Inner Git Repository Discovery.\n   - For output filenames: derive from the symlink basename (e.g.,\n     `Alwazeh Sami_grading.md`).\n\n### When Symlinks Are Missing\n\nIf no name-based symlinks exist in the CWD, use the student directory name\ndirectly as the identifier. Apply the standard Name Parsing from Repository\nBasename rule to extract first and last name.\n\n## Broken Name-Symlinks (Missing Repository)\n\nA name-based symlink whose target does NOT exist (broken symlink, detected\nwith `[ -L \"$entry\" ] && [ ! -e \"$entry\" ]`) means: **the student has not yet\nsubmitted the URL of their assignment repository.** (The conventional\nplaceholder target name is `KEIN-GITHUB-REPO`, but ANY missing target\nqualifies — not just that exact name.)\n\n### Protocol\n\n1. **Do NOT stop and do NOT skip.** A broken name-symlink is a valid grading\n   case, not an error. Bulk grading continues; the student is graded as a\n   no-repository case.\n2. **Evaluation**: no repository → no evidence → ALL assignments are rated\n   0% (Quality `—`, Factor `—`, Effective 0%) and the final result is\n   `Endbewertung: 0\u002F100`.\n3. **Outputs (bulk mode)**: the student receives a normal `\u003Cname>_grading.md`\n   (mandatory structure per the consuming skill's report format, every\n   section explicitly stating that no repository exists and no evidence is\n   available) and a `\u003Cname>_email.json`. They appear in `GRADINGS.md` with\n   0\u002F100 and in the aggregated `EMAIL.json`.\n4. **Email lookup**: proceed with the normal database lookup (by name). The\n   standard Missing Email Address Handling applies unchanged.\n5. **Email emphasis (MANDATORY)**: the email body MUST prominently state that\n   submitting the repository URL is REQUIRED for a positive assessment — see\n   Missing Repository Emphasis under Email Body Format.\n6. **Single-repo mode**: when the explicitly provided repository path is a\n   broken symlink, apply the same treatment (no-repo grading report +\n   emphasis email) instead of stopping.\n7. **No git commands** are run for these students (nothing to pull or\n   inspect); the Pre-Grading Verification is skipped for them.\n\n## Output Expectations\n\nThis skill provides configuration and shared protocols. Consuming skills\nproduce:\n- Per-student `*_grading.md` files with individual feedback (second-person German).\n- Per-student `*_points.json` sidecars with the graded points (machine-readable).\n- `EMAIL.json` with personalized payloads (second-person body).\n- `noten.tsv` as the Google-Doc import table (see Noten-TSV section).\n- `GRADINGS.md` class-wide overview (where applicable).\n- `CLASS.md` anonymized class patterns (where applicable).\n","# Grading Shared Configuration\n\n## Purpose\n\nThis skill provides centralized configuration for all grading workflows,\nensuring consistent email formatting, address styles, second-person address\nthroughout all grading content, and database access across different\nassessment types.\n\n## Output Language: MANDATORY GERMAN\n\nAll student-facing content (grading reports, feedback, email bodies) MUST be \nwritten in natural German with proper UTF-8 umlauts (ä, ö, ü, ß). \nNever use English for content intended for students.\n\n## Usage\n\nImport this skill at the beginning of any grading-related skill or command.\nReference the sections below for consistent behavior.\n\n## Anrede-Gate (CRITICAL, Pflicht)\n\nDie Anrede (Du\u002FSie) wird **nicht** aus dem Klassennamen und **nicht** aus der\nDatenbank abgeleitet. Sie steht in genau einer Datei pro Kohorte:\n\n```\n\u003Csubject>-T\u002F\u003Ckohorte>\u002Fanrede.txt\n```\n\nDie Datei enthält genau ein Wort: `Sie` oder `Du`.\n\nAblauf:\n\n1. `-T`-Repo-Wurzel und Kohorten-Ordner bestimmen (der Lauf-Ordner\n   `\u003Ckohorte>\u002F\u003CYYYY-MM-DD>_\u003Cslug>\u002F` liegt darin; `_class` zeigt auf das\n   öffentliche Schwester-Repo).\n2. Anrede mit dem mitgelieferten Skript lesen:\n\n   ```bash\n   ANREDE=$(~\u002F.opencode\u002Fskills\u002Fgrading-shared\u002Fread-anrede.sh \\\n       \"$T_REPO\u002F$KOHORTE\u002Fanrede.txt\") || exit 1\n   ```\n\n3. `Du` → informell, `Sie` → formell. Die Anrede gilt für **alle**\n   studentengerichteten Texte (E-Mails, `*_grading.md`, `GRADINGS.md`,\n   `CLASS.md`).\n\n**Stop-Regel (CRITICAL):** Fehlt die Datei, ist sie leer oder enthält sie\netwas anderes als `Sie`\u002F`Du`, sofort **STOPPEN** — vor dem ersten Schreibzugriff\n— und den Pfad samt Soll-Inhalt auf Deutsch melden:\n\n```\nFEHLT: \u002Fhome\u002Fgeorg\u002Fr\u002FGRG-WMC-T\u002F3aaif\u002Fanrede.txt\nBitte die Datei anlegen und genau 'Sie' oder 'Du' hineinschreiben.\n```\n\nEs gibt **keinen** Fallback auf eine Klassenliste und kein Raten. Das Gate gilt\nfür `repograde`, `knowledge-assessment`, `knowledge-exam` und `projectgrade`;\nes läuft immer vor dem ersten Schreibzugriff eines Laufs.\n\n## Lauf-Umgebung (Grading)\n\nRepositorien-basierte Läufe (`repograde`) starten im Grading-Arbeitsverzeichnis\n`~\u002Fgits\u002F\u003Ckohorte>-\u003Cfach>\u002F` (z. B. `~\u002Fgits\u002F3aaif-wmc\u002F`). Das ist **kein**\nGit-Repo; es enthält:\n\n```\n~\u002Fgits\u002F3aaif-wmc\u002F_class            -> \u002Fhome\u002Fgeorg\u002Fr\u002FGRG-WMC\u002F3aaif\n~\u002Fgits\u002F3aaif-wmc\u002FAbazi Hamza       -> Hamza473A        (Namens-Symlink)\n~\u002Fgits\u002F3aaif-wmc\u002FHamza473A\u002F        (Schüler-Repo, gelesen)\n```\n\n- `_class` zeigt in den öffentlichen Klassenordner des Teaching-Repos. Immer\n  mit `readlink -f` auflösen: `~\u002Fr` ist selbst ein Symlink auf\n  `repos\u002Fgeorgernstgraf\u002F`, und das Ziel kann mit oder ohne Schrägstrich enden.\n- Das private Test-Repo ist das **Schwesterverzeichnis** mit Suffix `-T`:\n  `\u002F…\u002FGRG-\u003CFACH>\u002F\u003Ckohorte>` → `\u002F…\u002FGRG-\u003CFACH>-T`.\n- Lauf-Ordner (alle Ausgaben eines Laufs, inkl. `noten.tsv`):\n  `\u003C…>-T\u002F\u003Ckohorte>\u002F\u003CYYYY-MM-DD>_\u003Cslug>\u002F` — bei Bedarf anlegen.\n- Anrede-Datei: `\u003C…>-T\u002F\u003Ckohorte>\u002Fanrede.txt`.\n- `roster.tsv` (aus `wu … roster`) ist ein Zwischenprodukt und liegt unter\n  `\u002Ftmp\u002Fopencode\u002F`, nie im Lauf-Ordner und nie im Commit.\n- `knowledge-assessment` läuft dagegen direkt im `-T`-Repo; der Lauf-Ordner\n  `\u003Ckohorte>\u002F\u003CYYYY-MM-DD>_\u003Cslug>\u002F` ist dort CWD bzw. wird angelegt.\n\n## Class Configuration\n\n### Name Parsing from Repository Basename (CRITICAL)\n\nRepository directory names follow the convention `Lastname Firstname`. The **last\nword** is always the first name; **everything before it** is the last name. This\napplies to compound last names as well.\n\n**Parsing rule:** Split the basename on spaces → `parts[0:-1]` = last name,\n`parts[-1]` = first name.\n\n| Repository Basename | Last Name | First Name |\n|---------------------|-----------|------------|\n| `Umlauf Ellen` | Umlauf | Ellen |\n| `Quintero Castañeda Nicolas` | Quintero Castañeda | Nicolas |\n| `Prodanovic Jovana` | Prodanovic | Jovana |\n| `Wahringer Tobias` | Wahringer | Tobias |\n| `Leeb Samuel` | Leeb | Samuel |\n| `Zore Elsner Marija` | Zore Elsner | Marija |\n\n**Wrong vs Correct — Formal salutation:**\n```\n❌ Sehr geehrte Frau Ellen,        ← used first name as last name\n✅ Sehr geehrte Frau Umlauf,       ← correct last name\n\n❌ Sehr geehrter Herr Nicolas,      ← used first name as last name\n✅ Sehr geehrter Herr Quintero Castañeda,  ← correct compound last name\n\n❌ Sehr geehrter Herr Tobias,       ← used first name as last name\n✅ Sehr geehrter Herr Wahringer,    ← correct last name\n```\n\n**Wrong vs Correct — Informal salutation:**\n```\n❌ Liebe Umlauf,                    ← used last name as first name\n✅ Liebe Ellen,                     ← correct first name\n\n❌ Lieber Quintero Castañeda,       ← used last name as first name\n✅ Lieber Nicolas,                  ← correct first name\n```\n\n## Email Generation Protocol\n\n### Greeting Formulas\n\n**Formal Address** (uses **last name** — see Name Parsing section above):\n- Female: `Sehr geehrte Frau [Last Name],`\n- Male: `Sehr geehrter Herr [Last Name],`\n- Unknown gender: `Guten Tag [First Name] [Last Name],`\n\n**Informal Address** (uses **first name** — the last word in the basename):\n- Any gender: `Liebe [First Name],` or `Lieber [First Name],`\n- Female preference: `Liebe [First Name],`\n- Male preference: `Lieber [First Name],`\n- Unknown gender: `Guten Tag [First Name] [Last Name],`\n\n**Concrete examples:**\n\n| Basename | Gender | Formal Greeting | Informal Greeting |\n|----------|--------|-----------------|-------------------|\n| `Umlauf Ellen` | F | `Sehr geehrte Frau Umlauf,` | `Liebe Ellen,` |\n| `Quintero Castañeda Nicolas` | M | `Sehr geehrter Herr Quintero Castañeda,` | `Lieber Nicolas,` |\n| `Wahringer Tobias` | M | `Sehr geehrter Herr Wahringer,` | `Lieber Tobias,` |\n\n### Closing Formulas\n\n**Formal Address:**\n```\nMit freundlichen Grüßen,\n\n   Georg Graf\n```\n\n**Informal Address:**\n```\nLieben Gruß,\n\n   Georg Graf\n```\n\n### Automatic Assessment Disclaimers\n\nEvery grading email body MUST include a disclaimer paragraph directly below the greeting. The wording depends on the class's address style (Formal\u002FInformal) and the assessment type (Assignments\u002FProjects vs. Knowledge-Checks):\n\n**Formal Address (Sie):**\n- **Assignments \u002F Projects \u002F General (Leistungsfeststellung):**\n  `Im Folgenden finden Sie die automatische Beurteilung Ihrer Leistungsfeststellung. Bitte beachten Sie, dass diese Beurteilung Irrtümer enthalten kann.`\n- **Knowledge-Checks \u002F Mini-Exams (Knowledge-Check):**\n  `Im Folgenden finden Sie die automatische Beurteilung Ihres Knowledge-Checks. Bitte beachten Sie, dass diese Beurteilung Irrtümer enthalten kann.`\n\n**Informal Address (Du):**\n- **Assignments \u002F Projects \u002F General (Leistungsfeststellung):**\n  `Im Folgenden findest du die automatische Beurteilung deiner Leistungsfeststellung. Bitte beachte, dass diese Beurteilung Irrtümer enthalten kann.`\n- **Knowledge-Checks \u002F Mini-Exams (Knowledge-Check):**\n  `Im Folgenden findest du die automatische Beurteilung deines Knowledge-Checks. Bitte beachte, dass diese Beurteilung Irrtümer enthalten kann.`\n\n### Gender Determination\n\nGender is determined from the **first name** (the last word in the repository\nbasename). Do NOT use the full basename string for gender lookup.\n\nWhen gender is not clear from the first name:\n1. Use class records or submission wording as additional context.\n2. If still uncertain, use neutral fallback: `Guten Tag [First Name] [Last Name],`\n3. Keep class-based closing formula (formal or informal based on class).\n4. Flag case for manual review in the output files.\n\n## Second-Person Address in Grading Content\n\n### Requirement\n\nALL grading content must address the student directly in the second person,\nmatching the email salutation style. This applies to:\n- `*_grading.md` files (repograde, knowledge-assessment, projectgrade)\n- Email body text in `EMAIL.json`\n\n### Pronoun and Verb conjugation\n\n| Style | Pronoun | Verb Conjugation | Example |\n|-------|---------|-----------------|---------|\n| Formal (Sie) | Sie | 3rd person plural formal | \"Sie haben die Abgabe gut gelöst\" |\n| Informal (Du) | Du | 2nd person informal | \"Du hast die Abgabe gut gelöst\" |\n\n### Grading Content Examples\n\n**Formal Address (Sie) - Wrong ❌:**\n```\nDer Student hat die Abgabe gut gelöst. Er hat sich bemüht.\n```\n\n**Formal Address (Sie) - Correct ✅:**\n```\nSie haben die Abgabe gut gelöst. Sie haben sich bemüht.\n```\n\n**Informal Address (Du) - Wrong ❌:**\n```\nDer Schüler hat die Abgabe gut gelöst. Er hat sich bemüht.\n```\n\n**Informal Address (Du) - Correct ✅:**\n```\nDu hast die Abgabe gut gelöst. Du hast dich bemüht.\n```\n\n### Gender-Neutral Handling for Unclear Cases\n\nWhen student gender cannot be determined:\n1. Use formal \"Sie\" (works as gender-neutral in written German).\n2. Use gender-neutral adjective forms where possible.\n3. Use neutral greeting fallback: `Guten Tag [First Name] [Last Name],`\n4. Flag in output files for manual review.\n\n### Consistency Checklist\n\nBefore finalizing any grading output, verify:\n- [ ] Email greeting matches body address style (Sie or Du)\n- [ ] All pronouns in body refer to student as \"Sie\" or \"Du\"\n- [ ] No third-person references to the student (\"der Schüler\", \"die Studentin\")\n- [ ] Verb conjugation matches address style\n- [ ] Closing formula matches address style\n\n## Grading Methodology (CRITICAL)\n\nAll grading workflows MUST evaluate student work through pure AI reasoning and\njudgment. The following rules apply to every grading skill (`repograde`,\n`knowledge-assessment`, `projectgrade`) and every subagent used within them.\n\n### Pure AI Evaluation\n\n- **Grading is performed through AI reasoning alone.** The agent must read,\n  understand, and evaluate each student submission by directly comparing it\n  against the task instructions and expected solutions. The evaluation must be\n  a direct intellectual act by the AI, not a mechanical or automated process.\n- **No scripts or programs for evaluation.** Under no circumstances may agents\n  write, invoke, or rely on scripts, programs, test runners, linters, or\n  automated checkers to assess student work. Grading must never be reduced to\n  a mechanical operation.\n- **Programmatic output formatting is allowed.** Using `json.dump()` or similar\n  serializers to write `EMAIL.json` or `*_email.json` files is permitted. The\n  prohibition applies only to the act of grading\u002Fassessing the student's work\n  itself — not to output formatting helpers.\n\n### Available Inputs Per Evaluation\n\nFor every student submission, the agent has access to the following inputs and\nmust base its evaluation exclusively on them:\n\n1. **Task instructions** — the assignment description (`Aufgabe.md` or the\n   `## Aufgabe` section of a Tages-README), exam questions, or project\n   requirements that define what was asked of the student.\n2. **Expected solutions** — the reference solution or answer key against which\n   student work is compared.\n3. **Student submission** — the individual student's work (repository commits,\n   answer file, code changes) to be evaluated.\n\n### Handling Large Volumes\n\n- **For bulk or multi-student grading, use sub-agents.** Each sub-agent\n  evaluates one student or repository independently, using the same pure AI\n  reasoning approach.\n- Sub-agents MUST follow all rules defined in this skill, including the\n  no-script grading requirement.\n- The orchestrating agent delegates work to sub-agents but must never write\n  evaluation scripts or batch-processing programs.\n\n## Database Access\n\n### Databases\n\n| Database | Path | Purpose |\n|----------|------|---------|\n| UploadThing | `\u002Fhome\u002Fgeorg\u002FOneDrive\u002Fuploadthing.db` | Repository grading |\n| Vacuum | `vacuum.db` (current directory) | Knowledge-check grading (must exist at start; error if missing) |\n\n### Schema\n\nTable: `users`\n\n| Column | Description |\n|--------|-------------|\n| `email` | Student email address |\n| `name` | Full student name |\n| `klasse` | Class identifier |\n\n### Lookup Protocol\n\n1. Match student by name (handle variations and partial matches).\n2. Normalize class comparison to uppercase: `WHERE UPPER(klasse) = UPPER(?)`.\n3. Retrieve `email` and `klasse` columns.\n4. Use `klasse` only for reporting; the address style comes from the\n   Anrede-Gate (`anrede.txt`), never from the class name.\n5. If `vacuum.db` is missing at start, stop immediately with error.\n6. Track all students with missing email addresses.\n\n## Missing Email Address Handling (CRITICAL)\n\nWhen generating `EMAIL.json`, if any student's email address cannot be found in\nthe database, the workflow MUST stop before generating the file.\n\n### Protocol\n\n1. **Collect all lookups**: Perform database lookup for all students first.\n2. **Check for missing emails**: After all lookups, identify students without\n   valid email addresses.\n3. **If any are missing**:\n   - STOP immediately.\n   - Do NOT generate `EMAIL.json`.\n   - Present ALL unresolved names to the user in a clear list:\n     ```\n     Die folgenden Schüler\u002Finnen konnten nicht in der Datenbank gefunden werden:\n     - [Name 1]\n     - [Name 2]\n     - ...\n     \n     Bitte aktualisieren Sie die Datenbank und starten Sie den Vorgang erneut.\n     ```\n   - Wait for user to update the database.\n   - User should confirm when database is updated.\n   - Retry the entire grading process.\n4. **Only proceed**: Generate `EMAIL.json` when ALL students have valid email\n   addresses.\n\n### Rationale\n\n- Prevents incomplete or invalid email payloads.\n- Ensures all students receive their feedback.\n- Forces database maintenance rather than workarounds.\n\n## Email JSON Structure\n\n```json\n[\n  {\n    \"mailto\": \"student@example.com\",\n    \"subject\": \"[Subject based on context]\",\n    \"body\": \"[Full personalized assessment in German]\"\n  }\n]\n```\n\n### Body Requirements\n\n- Language: German\n- Address student directly in second person (Sie or Du based on class)\n- Include full assessment content (long emails expected)\n- Preserve paragraph spacing and readable newline structure\n- Include greeting at start\n- Include closing formula at end\n- UTF-8 encoding with natural German umlauts (ä, ö, ü, ß)\n\n### JSON Escaping Rules (CRITICAL)\n\nThe `body` field often contains multi-paragraph German text with newlines, quotes,\nand special characters. **Improper escaping is the most common cause of broken\nemail JSON files.** The following rules MUST be followed:\n\n1. **All JSON string values must be valid JSON.** Newlines inside the `body`\n   field must use the escaped sequence `\\n` (two characters: backslash, `n`), NOT\n   literal line breaks. Double quotes must be escaped as `\\\"`, backslashes as `\\\\`,\n   and tabs as `\\t`.\n\n2. **No literal newlines inside JSON string values.** A file like this is INVALID:\n   ```json\n   \"body\": \"Sehr geehrte Frau Huber,\n\n   Sie haben die Abgabe gut gelöst.\"\n   ```\n   The correct form is:\n   ```json\n   \"body\": \"Sehr geehrte Frau Huber,\\n\\nSie haben die Abgabe gut gelöst.\"\n   ```\n\n3. **Validate before writing.** Every `*_email.json` file must parse successfully\n   with a standard JSON parser. After writing, verify with:\n   ```bash\n   python3 -c \"import json; json.load(open('FILE_email.json'))\"\n   ```\n\n4. **Detect double-escaped newlines.** After loading the JSON, check that the\n   `body` field does not contain literal `\\n` (backslash followed by n, which\n   indicates the file had `\\\\n` — double-escaped newlines):\n   ```bash\n   python3 \u003C\u003C 'PYEOF'\n   import json\n   d = json.load(open('FILE_email.json'))\n   body = d[0]['body']\n   assert '\\\\n' not in body, 'body contains literal backslash-n — double-escaped newlines'\n   print('OK')\n   PYEOF\n   ```\n   If validation fails, fix by reading the file with `json.load()`, replacing\n   literal `\\n` in the body with actual newlines, and re-writing with\n   `json.dump(..., ensure_ascii=False)`.\n\n5. **MUST use programmatic JSON writing.** You MUST use `json.dump()` or\n   `json.dumps()` to write `*_email.json` or `EMAIL.json`. Never construct JSON\n   via string interpolation, f-strings, or template languages. Always use\n   `ensure_ascii=False` to preserve UTF-8 umlauts (ä, ö, ü, ß). This guarantees\n   correct escaping of all special characters automatically.\n\n## Constraints\n\n- All grading content must be written in German.\n- All grading content must use second-person address (Sie or Du).\n- Never use third-person to refer to the student being graded.\n- UTF-8 encoding with natural German umlauts (ä, ö, ü, ß); never replace with\n  transliterations (ae, oe, ue) unless source material requires it.\n- Trailing comma required in all salutations.\n- Two newlines before signature line.\n- Three-space indentation before `Georg Graf`.\n- Never guess gender - use neutral fallback when uncertain.\n- Flag uncertain cases for manual review.\n- Grading must be performed through pure AI evaluation; never write scripts,\n  programs, or automated checkers to assess student work. For bulk grading,\n  use sub-agents instead (see `## Grading Methodology (CRITICAL)` above).\n\n## Repository Analysis Protocol\n\nShared pre-grading verification and analysis steps for all repository-based\ngrading skills (`repograde`, `projectgrade`).\n\n### Pre-Grading Verification\n\nBefore inspecting repository content, verify repository state:\n\n1. Navigate to the student repository directory.\n2. Run the Inner Git Repository Discovery to locate the actual Git working\n   directory inside the student directory.\n3. Run `git pull` inside the Git working directory to ensure latest version is\n   checked out.\n4. Run `git status` inside the Git working directory to check for uncommitted\n   changes.\n5. If uncommitted changes exist, STOP IMMEDIATELY and report to user.\n6. If pull fails or reports errors, STOP IMMEDIATELY and report to user.\n\n### Discovery\n\n- Identify relevant branches.\n- Enumerate commits while avoiding duplicate SHA processing.\n- Collect commit metadata: SHA, author, date, branch context, and message.\n\n### Per-Commit Inspection\n\nFor every relevant commit, inspect actual changes with commands equivalent to:\n\n- `git show --stat --summary \u003Csha>`\n- `git show --format=fuller --unified=3 \u003Csha>`\n\nUse diff content, not filenames alone, to identify what the student worked on.\n\n### Topic Detection\n\nDetect technical topics from the diffs and distinguish substantive work from\nsuperficial edits.\n\n- substantive: meaningful implementation, debugging, refactoring, feature work.\n- superficial: formatting-only edits, whitespace changes, trivial renames,\n  auto-generated files without meaningful modification.\n\nConsider common patterns across JavaScript, Java, C#, SQL, CSS, HTML, and\ngeneral programming constructs.\n\n### Branch and Activity Analysis\n\n- Focus primarily on `main`, but highlight significant non-main branch work.\n- Count commits over time.\n- Detect inactive gaps between first and last relevant commits.\n- Use evidence-based diligence signals such as `high`, `medium`, or `low`.\n\n## Repository Structure Discovery\n\nStudent repositories may use a two-level directory structure:\n\n```\nCWD\u002FSTUDENT_DIR\u002FINNER_PROJECT\u002F.git\n```\n\nThe `.git` directory (or `.git` file) is one level deeper than the student\ndirectory. This section defines how to locate the actual Git working directory\nbefore running any git commands.\n\n### Inner Git Repository Discovery\n\nFor each student repository, determine the correct Git working directory:\n\n1. **Enter the student directory** (or follow the symlink to its target).\n2. **Search for `.git`** inside the student directory up to 2 levels deep.\n3. The parent directory of the found `.git` is the **Git working directory**.\n4. All `git` commands (`pull`, `log`, `show`, `status`, `diff`) MUST be run\n   inside the Git working directory.\n5. Derive output filenames (`\u003Cbasename>_grading.md`, `\u003Cbasename>_email.json`)\n   from the **student directory basename** (the top-level name or the symlink\n   name), NOT from the inner project folder name.\n\n### Example\n\n```\n2ahwii-swp\u002Falw240094\u002FSWP-2025-26\u002F.git\n            ^^^^^^^^^ student dir basename (used for output filenames)\n                       ^^^^^^^^^^ Git working directory (where git commands run)\n```\n\n## Assignment Discovery Protocol (Aufgaben-Discovery)\n\nShared assignment source discovery for `repograde` and `projectgrade`.\nAssignments (Aufgaben) may exist in multiple locations. The grading skill MUST\ndiscover and merge all sources into a unified assignment list before matching\nagainst student commits.\n\n### Terminology (CRITICAL)\n\n- **Abgabe** is the canonical term for the graded unit in ALL output: emails,\n  `*_grading.md`, `GRADINGS.md`, `CLASS.md`, scoring tables and the Noten-TSV.\n  User-visible content spells it out (\"Abgabe 1\", \"Abgabe 2\") with a sequential\n  number — never \"Aufgabe\", \"Übung\", \"Hausübung\", \"HÜ\", or \"homework\".\n- **Aufgabe** (plural: **Aufgaben**) survives only as the name of the task\n  document\u002Fartifact: `Aufgabe.md`, the `## Aufgaben` index in the cohort README,\n  and the assignment-discovery sources below. It is the task that was given,\n  not the submission that is graded.\n- **Übung** is neither: Übungen (e.g. \"Jetzt du!\") are in-class\n  Mitarbeits-Elemente and MUST never appear in the assignment list.\n- The word **Hausübung** is legacy vocabulary. It is recognized only inside\n  legacy sources (see below) and never used in new output.\n\n### Source 1: Cohort README (via `_class` Symlink, Preferred)\n\nCheck for a `_class` symlink in the CWD. If it exists, follow it and read the\ncohort README (`README.md`) inside the linked class folder.\n\n#### 1a: Canonical Assignment List\n\nLook for a `## Aufgaben` section near the top of the cohort README. It is a\nspare bullet-list index with one line per assignment:\n\n```markdown\n## Aufgaben\n\n- 2026-09-29: Seite dekorieren (CSS) → `2026-10_XX_css-basics\u002F`\n```\n\nParse each bullet as `(iso_date, short_description, optional_relative_link)`:\n\n- A relative link on the bullet is a **Markdown link** whose target is the\n  lesson folder path relative to the cohort README directory, e.g. the bullet\n  above may equivalently carry `[2026-10_XX_css-basics\u002F](2026-10_XX_css-basics\u002F)`;\n  the link target is a lesson folder (Tagesordner) whose assignment content is\n  read from there (see Source 2). The list is an INDEX only — the task text\n  lives in the lesson folder, never in the list itself.\n- Entries without a link are matched by date\u002Ftopic against the other sources.\n- Grading is read-only: if the canonical list is missing, do NOT create it.\n  Report its absence as a note.\n\n#### 1b: Dated Sections in the Cohort README\n\nSections with `## YYYY-MM-DD` headings may contain assignment text directly.\nExtract the date from the heading and the assignment content from the section\nbody. Relative deadlines (\"bis nächste Woche 00:00\") are resolved via the wu\ntermine (see Deadline Calculation).\n\n### Source 2: Per-Lesson Folders inside `_class`\n\nScan `_class\u002F` for subdirectories matching `\u003CYYYY-MM-DD>_\u003Ctopic>`,\n`\u003CYYYY-MM-DD>__\u003Ctopic>` (create-lesson convention, double underscore), or an\nambiguous `\u003CYYYY-MM>_\u003CX>_\u003Ctopic>` form (e.g. `2026-10_XX_css-basics`) whose\ndate is resolved via Git (see Git Folder Provenance Resolution below).\n\nFor each matching directory, check for assignment content in this order:\n\n1. `Aufgabe.md` — dedicated assignment file (canonical)\n2. `README.md` — Tages-README with an assignment section under the heading\n   `## Aufgabe` or `## ? Aufgabe`\n3. Legacy files (data from earlier school years only): `Hausübung.md`,\n   `Angabe_HÜ.md`, or a README section under `## Hausübung` \u002F `## ? Hausübung`.\n   If the legacy section states \"Keine Hausübung\", skip this lesson.\n\nWhen a file is found:\n- Extract the date from the directory name — a full `YYYY-MM-DD` is the\n  CANDIDATE assignment date, subject to the Git Folder Provenance check below\n  (a renamed folder keeps the date of its OLDEST name).\n- Extract the topic from the directory name and\u002For the file content.\n- Read the file content for assignment details.\n- Add to the assignment list: `(iso_date, topic, content)`.\n\n#### Reference Pattern (Indirection to a Prepared Lesson)\n\nThe `## Aufgabe` section of a Tages-README may be a REFERENCE rather than task\ntext, for example: \"Wir haben Lesson `KM3-03-css-basics` gemacht, die Aufgabe\nist die Aufgabe von dort.\" Detection: a lesson reference matching\n`\u003CPREFIX>-\u003CNN>-\u003Cslug>` and\u002For a relative link into `unterricht\u002F`.\n\nResolution:\n\n1. Resolve the lesson folder relative to the teaching-repo root:\n   `\u003Crepo-root>\u002Funterricht\u002F\u003CPREFIX>-\u003CNN>-\u003Cslug>\u002F` (from a cohort folder one\n   level below the repo root this is `..\u002Funterricht\u002F…`, from two levels\n   `..\u002F..\u002Funterricht\u002F…`).\n2. Read the assignment content from that folder in this order: `aufgabe.md`,\n   `hausaufgabe.md` (legacy), the Tages-README template\n   (`\u003CPREFIX>-\u003CNN>-\u003Cslug>.md`), or the `## Aufgabe` section of the lesson.\n3. Assignment date and deadline stay cohort-side (folder name \u002F Git \u002F wu);\n   only the CONTENT is resolved remotely. An explicit deadline found in the\n   referenced content still wins (see Deadline Calculation).\n\n### Source 3: Legacy Cumulative File\n\nCheck for a `Hausübungen.md` file directly in the CWD or inside `_class\u002F`\n(may be a symbolic link; follow symlinks when reading). This is the legacy\nsingle-file approach from earlier school years. If found, parse it using the\nSemantic Date Extraction rules below.\n\n#### Semantic Date Extraction\n\n**You MUST perform a thorough semantic analysis of the entire file.**\n\nMany agents fail at this step because they:\n- Only read the first entry\n- Miss dates embedded in the text (not just headings)\n- Fail to convert German date formats to ISO\n- Skip entries that don't match a naive pattern match\n\nRequired steps:\n\n1. **Read the ENTIRE file** — do not stop after the first assignment entry.\n2. **Extract ALL date references** from:\n   - Headings: `## Hausübung vom 18. Februar`\n   - Inline dates: `Abgabe bis 25. Februar`\n   - Date ranges: `Zeitraum: 10.-18. März`\n   - Relative dates: `nächste Woche`, `in 2 Wochen` (convert to absolute)\n3. **Normalize ALL dates to ISO format** (YYYY-MM-DD).\n4. **Build an assignment list**: `[(iso_date, topic, content), ...]`.\n\nDate patterns to recognize:\n\n| Pattern | Example | Extraction |\n|---------|---------|------------|\n| `vom DD. Monat` | `vom 18. Februar` | 2026-02-18 (infer year) |\n| `vom DD. Monat YYYY` | `vom 18. Februar 2026` | 2026-02-18 |\n| `DD.MM.YYYY` | `18.02.2026` | 2026-02-18 |\n| `bis DD. Monat` | `Abgabe bis 25. Februar` | 2026-02-25 (deadline) |\n| `Zeitraum: DD.-DD. Monat` | `Zeitraum: 10.-18. März` | 2026-03-10 to 2026-03-18 |\n\nMonth name mapping:\n\n```\nJanuar = 01    Juli = 07\nFebruar = 02   August = 08\nMärz = 03      September = 09\nApril = 04     Oktober = 10\nMai = 05       November = 11\nJuni = 06      Dezember = 12\n```\n\nWhen year is not explicit, infer from the grading context or cross-reference\nwith commit dates.\n\n**Common Failure Pattern (AVOID THIS):**\n```\n❌ WRONG: Read Hausübungen.md, see first entry is \"vom 18. Februar\",\n         requested date is 2026-02-10, conclude \"no matching assignment\"\n✅ CORRECT: Parse ALL entries, find \"vom 10. Februar\" (matches),\n            \"vom 18. Februar\" (after cutoff), \"vom 25. Februar\" (after cutoff)\n```\n\n### Source 4: Per-Lesson Files in CWD\n\nScan the CWD itself for subdirectories matching `\u003CYYYY-MM-DD>_\u003Ctopic>` that\ncontain assignment content (same file search order as Source 2, including the\nReference Pattern). Apply Future Lesson Exclusion identically.\n\n### Git Folder Provenance Resolution\n\nA lesson folder's NAME is not a reliable assignment date: folders may be\nRENAMED forward (e.g. a folder created as `2026-09-22_html-basics` renamed to\n`2026-09-29_html-basics` when the next UE approached). The assignment date\nMUST therefore be resolved from the folder's Git provenance.\n\n**Pitfall (CRITICAL):** Never trust `git log --diff-filter=A` on the current\npath alone — renamed files register as `A` at the new path. Always use rename\ndetection (`-M` \u002F `--follow`).\n\nResolution (first match wins):\n\n1. **Reconstruct the name history** (run inside the `_class` target repo):\n   ```bash\n   git log -M --name-status --date=iso --format='%h %ad' --diff-filter=AR \\\n       -- \u003Cklasse>\u002F\u003Ccurrent-folder>*\n   ```\n2. **Rename found**: the name history is `oldest-name → … → current-name`.\n   The assignment date is the date encoded in the OLDEST name of that chain\n   (e.g. `2026-09-22_html-basics` → assignment date 2026-09-22). The rename\n   commit date itself is NEVER the assignment date.\n3. **No rename, full `YYYY-MM-DD` in the current name**: that date is the\n   assignment date.\n4. **No rename, ambiguous name** (e.g. `2026-10_XX_css-basics`): the\n   assignment date is the DATE PART of the oldest commit that added files in\n   the folder.\n5. **wu validation (all cases)**: the resolved date must be a real lesson\n   date from the wu termine; snap if needed (latest lesson date ≤ resolved\n   date). If both the current name date and the provenance date are real\n   lesson dates, the provenance date (oldest name) wins.\n6. **README cross-check**: a lesson date declared in the Tages-README (e.g.\n   \"HTML-Grundlagen (2026-09-22)\") is a cross-check signal. On conflict, the\n   provenance\u002Fwu date wins and the conflict MUST be reported to the user —\n   never silently resolved.\n7. **Same-day renames (preparation case)**: when the folder was renamed on\n   the SAME day it was created (e.g. created as `2026-09-29_css-basics` and\n   renamed in the same commit to `2026-10_XX_css-basics`), the folder was\n   prepared during that UE for a LATER UE — the oldest-name date is the\n   preparation date, NOT the assignment date. In that case the assignment\n   date comes from a dated cohort README section documenting the UE (e.g.\n   `## 2026-10-06__css`) or from the wu termine, and the ambiguity MUST be\n   reported. Note: lesson material may be DELETED after being moved into\n   `unterricht\u002F` — the cohort README reference section remains the\n   assignment source (Reference Pattern applies).\n\n### wu Termine Integration (MANDATORY)\n\nDerive the lesson address from the `_class` symlink target\n`…\u002FGRG-\u003CFACH>\u002F\u003Cklasse>\u002F` as `wu lesson \u003Cklasse>\u002F\u003Cfach>` (e.g.\n`_class -> GRG-WMC\u002F3aaif` → `wu lesson 3aaif\u002Fwmc`). Query the termine:\n\n```bash\nwu lesson 3aaif\u002Fwmc termine --json\n```\n\nThe termine list is used to:\n\n1. **Snap dates**: map git commit dates to real lesson dates (latest lesson\n   date ≤ commit date).\n2. **Compute deadlines**: deadline = next DISTINCT lesson date strictly after\n   the assignment date, at 00:00 (duplicate slots on one date count once;\n   holidays are respected automatically).\n3. **Resolve relative deadlines** (\"bis nächste Woche\", \"bis zur nächsten UE\").\n\n**Failure rule (CRITICAL):** If `wu` is not installed, fails, or returns no\ntermine, STOP IMMEDIATELY with a German error message (e.g. \"wu liefert keine\nTermine — Aufgabendaten und Deadlines können nicht aufgelöst werden.\"). Never\nguess dates or deadlines.\n\n### Future Lesson Exclusion\n\nPer-lesson directories with a date in the future represent prepared but\nnot-yet-held lessons. Their assignments MUST NOT be included in the unified\nassignment list.\n\n1. Determine today's date: `date +%Y-%m-%d`\n2. For each per-lesson directory, resolve the assignment date via Git Folder\n   Provenance Resolution.\n3. If `lesson_date > today`, skip this directory entirely.\n4. Do NOT include its assignment, regardless of whether an assignment section\n   exists in the file.\n\n### Merging All Sources\n\n1. Collect assignment entries from all sources into a single unified list.\n2. If multiple sources describe the same assignment, prefer the per-lesson\n   file content over the canonical list index and over the legacy cumulative\n   file.\n3. Sort the unified list by date.\n4. If no source provides any assignment entries, report this to the user and\n   grade based on available work only.\n\n## Aufgaben-Ablage-Ordnung (Order to Keep When Giving Assignments)\n\nThis section documents the expected repository layout. Keep to it when\ncreating or preparing assignments:\n\n1. **Kohorten-README** (`\u003Cklasse>\u002FREADME.md`): carry a spare canonical\n   `## Aufgaben` bullet list near the top — one line per assignment with ISO\n   date, a few words, and a relative link to the Tagesordner. The list is an\n   index only; the task text lives in the lesson folder.\n2. **Tagesordner** (`\u003Cklasse>\u002FYYYY-MM-DD_\u003Ctopic>\u002F`; the create-lesson double\n   underscore `YYYY-MM-DD__\u003Ctopic>` is also accepted): contains `Aufgabe.md`\n   or a `README.md` with an `## Aufgabe` section — full task text or a\n   reference to a Prepared Lesson (`unterricht\u002F\u003CPREFIX>-\u003CNN>-\u003Cslug>\u002F`).\n3. Ambiguous folder names like `YYYY-MM_XX_\u003Ctopic>` are allowed; the date is\n   resolved via the teaching repo's Git history and snapped to a wu lesson\n   date. Renaming a lesson folder forward is also fine — the discovery\n   attributes the date of the OLDEST name in the folder's Git history and\n   reports the rename.\n4. **Deadline**: state a concrete date (\"Abgabe per Push bis 2026-10-13\n   00:00\") or \"bis zur nächsten UE\" (resolved via wu). Vague formulations\n   that wu cannot resolve are not acceptable.\n5. Prepared but not-yet-held lessons (future dates) may already exist; they\n   are excluded automatically until the date arrives.\n6. Assignments NEVER go into a student repository.\n7. The word \"Übung\" marks in-class exercises, never assignments. Do not use\n   \"Hausübung\" for new content.\n\n## Deadline Calculation\n\n### Determining the Assignment Date\n\n1. Full `YYYY-MM-DD` in the lesson folder name → that date is the assignment\n   date.\n2. Ambiguous or renamed folder name (e.g. `2026-10_XX_css-basics`,\n   `2026-09-29_html-basics` renamed from `2026-09-22_html-basics`) → Git\n   Folder Provenance Resolution, snapped via wu to a real lesson date.\n3. Cohort README dated section → the heading date.\n\nThe assignment date MUST correspond to an actual lesson date from\n`wu lesson \u003Cklasse>\u002F\u003Cfach> termine --json`.\n\n### Determining the Deadline\n\nPrecedence:\n\n1. **Explicit deadline in the assignment text** (e.g. \"Abgabe per Push bis\n   2026-09-29 00:00\") ALWAYS wins. Parse ISO (`YYYY-MM-DD`) and German dates.\n2. **Otherwise**: deadline = next DISTINCT lesson date strictly after the\n   assignment date, at 00:00:00, taken from the wu termine. Duplicate slots\n   on the same date count once; this automatically respects holidays\n   (e.g. assignment 2026-10-13 → deadline 2026-10-20T00:00:00 when 10-13 is\n   followed by 10-20).\n3. **Relative deadlines** (\"bis nächste Woche\", \"bis zur nächsten UE\") are\n   resolved to the next lesson date at 00:00 via the wu termine.\n\nSubagents MUST take the deadline from the wu output (or an explicit deadline\nin the assignment text) and MUST use shell date arithmetic only for\nformatting, never for guessing:\n\n```bash\nwu lesson 3aaif\u002Fwmc termine --json\n```\n\n**Failure rule:** If wu is unavailable or returns no termine, STOP IMMEDIATELY\nwith a German error message. Never fall back to a fixed \"+7 days\" guess.\n\n### Commit Filtering by Deadline\n\n```bash\n# On-time commits (assignment ≤ commit ≤ deadline):\ngit log --after=\"2026-02-10\" --before=\"$DEADLINE\" --oneline\n\n# Late commits (commit after deadline):\ngit log --after=\"$DEADLINE\" --oneline\n```\n\n### Punctuality Classification\n\n| Latest relevant commit | Classification | Factor | Meaning |\n|------------------------|----------------|--------|---------|\n| ≤ DEADLINE             | pünktlich      | 1.0    | Full points possible |\n| > DEADLINE             | verspätet      | 0.75   | 25% penalty on achieved quality, regardless of how late |\n| No commits             | fehlend        | —      | 0 points for this assignment |\n\n### Deadline in Reports\n\nReports MUST mention the concrete deadline for each assignment, including the\nexact `YYYY-MM-DDT00:00:00` timestamp and whether the student's latest\nrelevant commit fell before or after it.\n\n## Bulk Grading Protocol\n\nShared concurrency and directory handling for `repograde` bulk mode.\n\n### Directory Exclusion\n\nWhen enumerating student repositories, **exclude**:\n- `_class` (assignment symlink)\n- Any directory starting with `_` (underscore prefix)\n- Any directory starting with `.` (dot prefix)\n- Any regular files (not directories and not symlinks)\n\nThese excluded entries are NOT student repositories and must never be graded.\n\n### Symlink Handling\n\nStudent repositories may be accompanied by name-based symlinks:\n\n```\nAlwazeh Sami -> alw240094\n```\n\nThese symlinks are aliases to student directories. They serve for name\nresolution (see `Student Name Resolution via Symlinks`) but are NOT\nseparate repositories. Only the target directories are graded.\n\nWhen enumerating:\n1. If a symlink points to another directory inside the CWD, skip it as a\n   candidate for grading — only the target directory is graded.\n2. Use the symlink's name for student identification.\n3. Follow the symlink to find the actual repository for git operations.\n4. If the symlink is BROKEN (target does not exist), it is NOT skipped and\n   NOT an error — see Broken Name-Symlinks (Missing Repository) below.\n\n### Concurrency\n\n- Default maximum: 5 repositories in progress at once.\n- When one grading run completes, start the next after an approximately\n  3-second delay.\n- Each subagent must derive its output stem from the repository basename and\n  write only `\u003Cbasename>_grading.md` plus `\u003Cbasename>_email.json`.\n- Subagents must never write shared `EMAIL.json`.\n\n### Aggregation\n\nAfter all subagents finish, the master workflow aggregates per-student\n`*_email.json` files into a single `EMAIL.json`.\n\n**Stop condition:** Verify the aggregation script exists:\n```bash\nSCRIPT=~\u002F.opencode\u002Fskills\u002Fgrading-shared\u002Faggregate-emails.py\nif [ ! -f \"$SCRIPT\" ]; then\n    echo \"ERROR: $SCRIPT not found\" >&2\n    exit 1\nfi\n```\nIf the script is missing, **STOP**, report the path to the user, and wait\nfor resolution before continuing.\n\n**Execution:** Run the script from the current working directory:\n```bash\n~\u002F.opencode\u002Fskills\u002Fgrading-shared\u002Faggregate-emails.py\n```\nIf the script exits with a non-zero status or reports errors on stderr,\n**STOP**, display its output to the user, and do not proceed.\n\n## Noten-TSV für den Google-Doc-Import (MANDATORY)\n\nJeder Lauf, der `EMAIL.json` erzeugt, erzeugt zusätzlich eine **Noten-TSV**, die\nper Copy-Paste in die Noten-Tabelle des Lehrers übernommen wird\n(`repograde` und `knowledge-assessment`).\n\n### Ablage und Umfang\n\n- Dateiname: `noten.tsv` im **Lauf-Ordner** des privaten `-T`-Repos:\n  `\u003Csubject>-T\u002F\u003Ckohorte>\u002F\u003CYYYY-MM-DD>_\u003Cslug>\u002Fnoten.tsv`\n  (Lauf-Ordner bei Bedarf anlegen, siehe Zielordner-Regel der Skills).\n- **Pro Lauf eine völlig neue Datei.** Keine Fortschreibung, keine\n  Zustandsdatei: die TSV enthält immer nur die Spalten dieses Laufs.\n\n### Layout\n\n- Spalte **A** = Vollname (Nachname Vorname, exakt wie `wu … roster` und die\n  `-T`-Dateinamen)\n- Spalte **B** = leer (Trennspalte)\n- ab Spalte **C** je Abgabe (Hausübung) bzw. je Knowledge-Check eine Spalte\n\n| Zeile | Inhalt |\n|-------|--------|\n| 1 | `Abgabe \u003Cvon>–\u003Cbis>` (z. B. `Abgabe 06.10.–13.10.2026`); bei WÜ `WÜ \u003CISO-Datum>` |\n| 2 | maximal erreichbare Punkte (100 je Abgabe; bei WÜ die Gesamtpunkte der Lösungsdatei) |\n| 3 | leer |\n| 4 ff. | Punkte je Schüler:in, ganzzahlig kaufmännisch gerundet, fehlend = `0` |\n\nBeispiel (Hausübungen):\n\n```\n\t\tAbgabe 06.10.–13.10.2026\tAbgabe 13.10.–20.10.2026\n\t\t100\t100\n\t\t\t\nAbazi Hamza\t\t80\t53\nAhmad Ali\t\t0\t0\n```\n\n### Datenquelle: Punkte-Sidecars\n\nJeder Subagent schreibt zusätzlich zu `_grading.md` und `_email.json` einen\nSidecar `\u003Cbasename>_points.json` (bei `repograde` der Repo-Basename, bei\n`knowledge-assessment` der Schüler:innen-Name; JSON-Array mit genau einem\nObjekt, geschrieben mit `json.dump(..., ensure_ascii=False)`):\n\nHausübung (`repograde`):\n\n```json\n[{\n  \"name\": \"Abazi Hamza\",\n  \"kind\": \"homework\",\n  \"klasse\": \"3AAIF\",\n  \"assignments\": [\n    {\"from\": \"2026-10-06\", \"to\": \"2026-10-13\",\n     \"label\": \"Tooling & Git\",\n     \"title\": \"Aufgabe 1: Tooling & Git (bis 13.10.2026)\",\n     \"max\": 100, \"points\": 80}\n  ]\n}]\n```\n\n`points` ist der Effective-Wert der Abgabe (`Quality × Factor`, also z. B.\n`52.5`), `max` ist `100` je Abgabe.\n\n`assignments` enthält **genau einen Eintrag je Aufgabe** des Laufs, nicht einen\nSammelwert über den ganzen Zeitraum. `title` ist optional und bestimmt den Text\nin Zeile 1 der TSV; er ist Pflicht, sobald zwei Aufgaben denselben Zeitraum\nhaben (gleiche Deadline), weil die Spalten sonst verschmelzen. Ohne `title`\nbleibt es beim automatischen `Abgabe \u003Cvon>–\u003Cbis>`.\n\nWird dieselbe Aufgabe mehrfach gestellt (etwa mit verschobenem Termin), ist sie\n**eine** Aufgabe: ein Eintrag, und es gilt der **spätere** Abgabetermin. Zwei\nSchreibweisen desselben Zeitpunkts (z. B. `2026-09-23` 23:59 und\n`2026-09-24T00:00:00`) sind kein zweiter Termin — dort gilt die Schreibweise,\ndie der Unterrichts-\u002FAngabepraxis entspricht.\n\nKnowledge-Check (`knowledge-assessment`):\n\n```json\n[{\n  \"name\": \"Abazi Hamza\",\n  \"kind\": \"knowledge\",\n  \"klasse\": \"3AAIF\",\n  \"exam\": \"2026-10-20\",\n  \"max\": 145,\n  \"points\": 88\n}]\n```\n\n`max` stammt aus den Gesamtpunkten der Lösungsdatei\n(`knowledge_\u003Cklasse>_\u003Cdatum>_solutions.md`), nie aus einer Annahme.\n\nDer Sidecar ist reine Ausgabeformatierung — die Bewertung selbst bleibt\nausschließlich KI-Beurteilung.\n\n### Erzeugung: Probelauf zuerst (CRITICAL)\n\n```bash\n# 1. Roster beschaffen (TSV: Name\u003CTAB>Klasse)\nwu lesson \u003CKLASSE\u002FFACH> roster > roster.tsv\n\n# 2. Probelauf — read-only, Tabelle geht nach stdout\n~\u002F.opencode\u002Fskills\u002Fgrading-shared\u002Femit-grade-tsv.py --roster roster.tsv\n\n# 3. Erst nach Sichtung der Tabelle wirklich schreiben\n~\u002F.opencode\u002Fskills\u002Fgrading-shared\u002Femit-grade-tsv.py --roster roster.tsv --write\n```\n\nRegeln:\n\n- Das Skript ist **ohne `--write` immer read-only**; der Probelauf ist Pflicht\n  und wird dem User gezeigt.\n- Zeilen kommen aus dem **Roster** (`wu lesson \u003CKLASSE\u002FFACH> roster`), nicht aus\n  den Abgaben — fehlende Abgaben erscheinen als `0`. Fällt `wu` aus: STOPP mit\n  deutscher Meldung (kein Raten, keine Alternativliste).\n- Namen werden normalisiert abgeglichen (`_` → Leerzeichen, Groß-\u002FKleinschreibung\n  egal). Abgaben ohne Roster-Eintrag werden angehängt **und** als Warnung\n  gemeldet; Roster-Einträge ohne Abgabe erscheinen mit `0`.\n- Widersprüchliche Maximalpunkte, gemischte Arten (Hausübung + Knowledge-Check)\n  oder fehlende Sidecars brechen mit Exitcode 1 ab.\n- Nach `--write` wird `noten.tsv` gemeinsam mit den übrigen Ausgaben im\n  `-T`-Repo committet.\n\n### Nachträgliche TSV für Läufe ohne Sidecars\n\nLäufe, die vor Einführung der Noten-TSV (2026-11-01) entstanden sind, haben\n`GRADINGS.md` und `EMAIL.json`, aber weder `*_points.json` noch `noten.tsv`.\nDiese Lücke wird nachgezogen, ohne die Bewertung neu zu machen:\n\n1. **Knowledge-Checks:** Punkte aus der Übersichtstabelle der `GRADINGS.md`\n   (Spalte „Gesamt\") — dort gibt es nur eine Spalte je Lauf.\n2. **Repo-Läufe:** Punkte **je Aufgabe** aus dem Abschnitt `## Endbewertung`\n   des jeweiligen `\u003Cname>_grading.md` (`Quality × Factor`, `max = 100`). Die\n   Übersichtstabelle der `GRADINGS.md` taugt dafür nicht — sie enthält nur die\n   aggregierte Endbewertung. Ein Lauf mit N Aufgaben ergibt N Spalten.\n3. Sidecars **außerhalb** des Repos erzeugen (z. B. unter `\u002Ftmp\u002Fopencode\u002F`) und\n   dort `emit-grade-tsv.py --out \u003Clauf>\u002Fnoten.tsv --write` aufrufen.\n   Die Sidecars sind hier reine Zwischenprodukte und werden **nicht**\n   committet; committet wird ausschließlich `noten.tsv`.\n4. Spaltenkopf je Aufgabe: `title` = `Aufgabe N: \u003CThema> (bis DD.MM.YYYY)`.\n   Thema und Deadline aus den Einzelgutachten (Mehrheitsschreibweise). Keine\n   `von`-Daten erfinden — die Abgabezeiträume sind oft nicht belegbar.\n5. **Namensabweichungen vor dem Lauf explizit mappen.** Gutachten führen oft\n   Kurzformen (`Moza Lukas`, `Figueroa Denise`), der Roster den vollen\n   WebUntis-Namen (`Moza Lukas, Kevin`, `Figueroa Castillo Karla Denise`).\n   Ohne Mapping erscheint die Person doppelt: einmal mit `0` aus dem Roster,\n   einmal angehängt mit ihren Punkten.\n6. **Verifikation:** die Summe der Einzelwerte muss die Zeile „Erreichte\n   Punkte\" des Gutachtens ergeben. Weicht sie ab, ist die **Quelldatei**\n   fehlerhaft — nicht die TSV. Solche Fälle dokumentieren und dem User\n   vorlegen, statt die Differenz einer Aufgabe zuzuschlagen.\n\n## Email Body Format (CRITICAL)\n\n\nAll grading email bodies MUST be clear ASCII text. This section defines the\nexact structure and formatting rules for every email body generated by any\ngrading skill.\n\n### Plain-Text Rule\n\nEmail bodies are plain ASCII text with **one exception**: code blocks with\nbacktick fences (e.g., ```sql ... ```) for short source-code snippets that\nhelp illustrate a grading point.\n\n**Forbidden in email bodies:**\n- Markdown headers (`##`, `###`, etc.)\n- Bold text (`**...**`)\n- Italic text (`*...*`)\n- Markdown tables\n- Markdown bullet lists (`- item` or `* item`)\n- Horizontal rules (`---`)\n- Any other Markdown formatting\n\n**Allowed in email bodies:**\n- Plain text paragraphs separated by blank lines.\n- Code blocks with backtick fences for short source-code snippets.\n- Natural German text with UTF-8 umlauts (ä, ö, ü, ß).\n\n### Structure for Assignment Grading Emails\n\nUsed by `repograde` skill (single-repo and bulk mode). The email body follows\nthis exact paragraph order:\n\n1. **Greeting** — per Greeting Formulas section above.\n\n2. **Automatic assessment disclaimer** — per Automatic Assessment Disclaimers section above.\n\n3. **Opening sentence** (address style dependent):\n   - Formal: `Ich habe Ihre Abgaben, welche im Zeitraum vom [Start-Datum] bis zum [End-Datum] aufgegeben waren, durchgesehen.`\n   - Informal: `Ich habe deine Abgaben, welche im Zeitraum vom [Start-Datum] bis zum [End-Datum] aufgegeben waren, durchgesehen.`\n   - Dates in German long format (e.g., \"18. Februar 2026\", \"4. März 2026\").\n\n3. **Commit summary** — Two to three sentences summarizing the commits\n   discovered in the student's repository during the grading period.\n   Mention overall activity, approximate number of substantive commits,\n   and any notable patterns (gaps, bursts, consistent work).\n\n4. **Assignment overview** — A concise list of all Abgaben,\n   each in one to two sentences. Format:\n\n   ```\n   Abgabe 1: [Topic\u002Fcontent summary in 1-2 sentences]\n   Abgabe 2: [Topic\u002Fcontent summary in 1-2 sentences]\n   ```\n\n   Use \"Abgabe\" spelled out with a sequential number. Never abbreviate\n   (no \"HÜ\", no \"A1\").\n\n5. **Per-assignment evaluation** — For each Abgabe, provide\n   a detailed discussion and evaluation on a 0–100% scale. Each evaluation\n   is a separate paragraph covering:\n   - What the student did well.\n   - What was missing or could be improved.\n   - Specific technical observations (with code snippets where helpful).\n   - A percentage rating for that individual assignment.\n\n6. **Coverage \u002F timeliness summary** — One short paragraph summarizing overall\n   Abgaben coverage and whether submissions were on time, late, partial, or\n   missing where relevant.\n\n7. **Recommendations and praise** — A warm, genuine paragraph with\n   improvement suggestions and subtle praise. See Praise Guidelines below.\n\n8. **Final score summary** — One short paragraph containing the final weighted\n   result in the form `Endbewertung: XX\u002F100 (XX%)`.\n\n9. **Closing** — per Closing Formulas section above.\n\nKeep email length reasonably consistent across students. Do not omit one of the\nparagraph groups above simply because a repository is weaker or stronger; if\nevidence is limited, say so briefly and continue with the same structure.\n\nFor students WITHOUT a repository (broken name-symlink), see the\nMissing Repository Emphasis below — that variant replaces paragraph groups\n3-6.\n\n### Missing Repository Emphasis (No-Repo Cases)\n\nFor a student whose repository is missing (broken name-symlink — see Broken\nName-Symlinks), the email body consists of: greeting, automatic assessment\ndisclaimer, then the emphasis paragraph as the FIRST content paragraph\n(adapted to the address style), a short consequences paragraph, and the\nclosing. The emphasis paragraph wording:\n\n- Informal: `Zu deinem Aufgaben-Repository liegt mir bislang kein Link vor. Das ist der mit Abstand wichtigste offene Punkt: Ohne die Bekanntgabe deines Repositories ist keine positive Beurteilung möglich. Sende mir bitte UMGEHEND den URL deines GitHub-Repositories an grafg@spengergasse.at — genau so haben wir es in der ersten UE vereinbart. Sobald das Repository verfügbar ist, werde ich alle Abgaben nachbewerten.`\n- Formal: `Zu Ihrem Aufgaben-Repository liegt mir bislang kein Link vor. Das ist der mit Abstand wichtigste offene Punkt: Ohne die Bekanntgabe Ihres Repositories ist keine positive Beurteilung möglich. Senden Sie mir bitte UMGEHEND den URL Ihres GitHub-Repositories an grafg@spengergasse.at — genau so haben wir es in der ersten UE vereinbart. Sobald das Repository verfügbar ist, werde ich alle Abgaben nachbewerten.`\n\nFollow with one short paragraph stating that all Abgaben in the grading\nperiod currently count 0% and that the final result is\n`Endbewertung: 0\u002F100 (0%)`.\n\n### Structure for Knowledge-Check Emails\n\nUsed by `knowledge-assessment` skill. Same plain-text rule applies.\n\n1. **Greeting** — per Greeting Formulas section above.\n\n2. **Automatic assessment disclaimer** — per Automatic Assessment Disclaimers section above.\n\n3. **Opening sentence** (address style dependent):\n   - Formal: `Ich habe Ihre Wissensüberprüfung vom [Datum] durchgesehen.`\n   - Informal: `Ich habe deine Wissensüberprüfung vom [Datum] durchgesehen.`\n\n4. **Score summary** — Total points achieved out of total possible points.\n\n4. **Question-by-question analysis** — Plain text paragraphs discussing\n   strengths and weaknesses in specific questions.\n\n5. **Recommendations and praise** — Same guidelines as assignment emails.\n\n6. **Solutions note** — `Ich habe die Datei mit den korrekten Lösungen in das Git-Repository hochgeladen.`\n\n7. **Closing** — per Closing Formulas section above.\n\n### Praise Guidelines\n\nPraise must be **subtle and understated**. Never effusive, never over the top.\n\n**Good praise examples:**\n- \"Die JOIN-Operationen haben Sie sauber umgesetzt.\"\n- \"Hier ist Ihnen eine klare Struktur gelungen.\"\n- \"Das zeigt ein gutes Verständnis der Konzepte.\"\n- \"An dieser Stelle war Ihre Lösung besonders durchdacht.\"\n\n**Bad praise examples (DO NOT USE):**\n- \"Hervorragende Arbeit! Brillant gelöst!\"\n- \"Sie sind ein wahrer Meister der Programmierung!\"\n- \"Phantastisch! Weiter so!\"\n- \"Ich bin beeindruckt von Ihrer überragenden Leistung!\"\n\nThe tone should feel like a calm, observant teacher noting genuine strengths\nwithout exaggeration. Let the facts speak for themselves. When a student did\nsomething well, state it matter-of-factly.\n\n### Examples\n\nWorked formal and informal assignment email bodies are in\n[EMAIL-EXAMPLES.md](.\u002FEMAIL-EXAMPLES.md). Use them as shape references, not as\ntext to copy verbatim.\n\n## Reporting Protocol\n\nAll grading content must be written in German and address the student directly\nin the second person (Sie or Du based on class). See the Second-Person Address\nsection above for pronoun usage and examples.\n\nThe `*_grading.md` files may use full Markdown formatting (headers, bold,\ntables, lists). Only the email body in `*_email.json` is restricted to plain\ntext per the Email Body Format section above.\n\nReports should include, where applicable:\n\n- repository overview\n- assignment-by-assignment summary\n- topic coverage\n- per-commit technical analysis\n- non-main branch activity\n- activity over time\n- inactive gaps\n- diligence assessment\n- final evaluation with `Endbewertung: XX\u002F100`\n\nWhen a consuming skill defines an exact mandatory report template, that template\noverrides any looser interpretation of this Reporting Protocol. In such cases,\nagents and subagents MUST follow the exact heading names, order, and section\nscope defined by the consuming skill and MUST NOT improvise alternative report\nstructures.\n\n## Student Name Resolution via Symlinks\n\nWhen the CWD contains name-based symlinks (`Lastname Firstname -> STUDENT_ID`),\nuse the symlink name to identify the student.\n\n### Resolution Protocol\n\n1. **Detect symlinks**: For each entry in the CWD, check if it is a symlink\n   (`[ -L \"$entry\" ]`) and if it points to another student directory within\n   the CWD.\n2. **Parse the symlink name**: Apply the standard Name Parsing from Repository\n   Basename rule (last word = first name, everything before = last name).\n3. **Resolve student identity**:\n   - For grading reports and `GRADINGS.md`: use the symlink name\n     (e.g., \"Alwazeh Sami\").\n   - For database lookup (email): use the symlink name.\n   - For git operations: follow the symlink to the target directory, then\n     apply Inner Git Repository Discovery.\n   - For output filenames: derive from the symlink basename (e.g.,\n     `Alwazeh Sami_grading.md`).\n\n### When Symlinks Are Missing\n\nIf no name-based symlinks exist in the CWD, use the student directory name\ndirectly as the identifier. Apply the standard Name Parsing from Repository\nBasename rule to extract first and last name.\n\n## Broken Name-Symlinks (Missing Repository)\n\nA name-based symlink whose target does NOT exist (broken symlink, detected\nwith `[ -L \"$entry\" ] && [ ! -e \"$entry\" ]`) means: **the student has not yet\nsubmitted the URL of their assignment repository.** (The conventional\nplaceholder target name is `KEIN-GITHUB-REPO`, but ANY missing target\nqualifies — not just that exact name.)\n\n### Protocol\n\n1. **Do NOT stop and do NOT skip.** A broken name-symlink is a valid grading\n   case, not an error. Bulk grading continues; the student is graded as a\n   no-repository case.\n2. **Evaluation**: no repository → no evidence → ALL assignments are rated\n   0% (Quality `—`, Factor `—`, Effective 0%) and the final result is\n   `Endbewertung: 0\u002F100`.\n3. **Outputs (bulk mode)**: the student receives a normal `\u003Cname>_grading.md`\n   (mandatory structure per the consuming skill's report format, every\n   section explicitly stating that no repository exists and no evidence is\n   available) and a `\u003Cname>_email.json`. They appear in `GRADINGS.md` with\n   0\u002F100 and in the aggregated `EMAIL.json`.\n4. **Email lookup**: proceed with the normal database lookup (by name). The\n   standard Missing Email Address Handling applies unchanged.\n5. **Email emphasis (MANDATORY)**: the email body MUST prominently state that\n   submitting the repository URL is REQUIRED for a positive assessment — see\n   Missing Repository Emphasis under Email Body Format.\n6. **Single-repo mode**: when the explicitly provided repository path is a\n   broken symlink, apply the same treatment (no-repo grading report +\n   emphasis email) instead of stopping.\n7. **No git commands** are run for these students (nothing to pull or\n   inspect); the Pre-Grading Verification is skipped for them.\n\n## Output Expectations\n\nThis skill provides configuration and shared protocols. Consuming skills\nproduce:\n- Per-student `*_grading.md` files with individual feedback (second-person German).\n- Per-student `*_points.json` sidecars with the graded points (machine-readable).\n- `EMAIL.json` with personalized payloads (second-person body).\n- `noten.tsv` as the Google-Doc import table (see Noten-TSV section).\n- `GRADINGS.md` class-wide overview (where applicable).\n- `CLASS.md` anonymized class patterns (where applicable).\n",{"name":5,"license":15,"metadata":38,"description":7,"compatibility":29},{"output":30,"category":31},"main","66ec3d7999eadae7047adbda7a3fca169794ee3c","skills\u002Fgrading-shared",0,"https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers","https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf","https:\u002F\u002Fwww.spengergasse.at\u002F","2026-11-11 16:51:38",{"source":8,"skillName":5,"commands":48,"prompt":58,"zipAvailable":59},[49,52,55],{"tool":50,"command":51},"npx","npx skills add georgernstgraf\u002Fopencode-helpers --skill grading-shared",{"tool":53,"command":54},"bunx","bunx skills add georgernstgraf\u002Fopencode-helpers --skill grading-shared",{"tool":56,"command":57},"pnpm","pnpm dlx skills add georgernstgraf\u002Fopencode-helpers --skill grading-shared","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers\u002Fblob\u002Fmain\u002Fskills\u002Fgrading-shared\u002FSKILL.md",true,[61,67,73,77],{"path":62,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":65,"sourceHtmlUrl":66},"aggregate-emails.py","SCRIPT","脚本",2683,"https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers\u002Fblob\u002Fmain\u002Fskills\u002Fgrading-shared\u002Faggregate-emails.py",{"path":68,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":71,"sourceHtmlUrl":72},"EMAIL-EXAMPLES.md","REFERENCE","参考文档",5518,"https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers\u002Fblob\u002Fmain\u002Fskills\u002Fgrading-shared\u002FEMAIL-EXAMPLES.md",{"path":74,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":75,"sourceHtmlUrl":76},"emit-grade-tsv.py",15560,"https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers\u002Fblob\u002Fmain\u002Fskills\u002Fgrading-shared\u002Femit-grade-tsv.py",{"path":78,"resourceType":63,"resourceTypeDesc":64,"sizeBytes":79,"sourceHtmlUrl":80},"read-anrede.sh",1405,"https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers\u002Fblob\u002Fmain\u002Fskills\u002Fgrading-shared\u002Fread-anrede.sh",[82,96,122,138,153,165],{"skillNo":83,"name":84,"nameZh":6,"description":85,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":86,"categories":87,"keywords":89,"publishedAt":33},"01M6CVKZ762FN4SW18V26P65TD","teach","Teach the user a new skill or concept, within this workspace.","https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers\u002Fblob\u002Fmain\u002Fskills\u002Fteach\u002FSKILL.md",[88],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[84,90,91,92,93,94,95],"false","new","concept","user","new skill","workspace",{"skillNo":97,"name":98,"nameZh":6,"description":99,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":100,"categories":101,"keywords":102,"publishedAt":33},"01M6CVKVT6ZVYN2XPESS5EGDKE","sync-upstream-skills","Re-transplant owned skills from their mattpocock upstream after a git pull, applying upstream content changes while preserving opencode-native frontmatter. Also checks for duplicate skill names and dangling cross-skill references. Use when the user says \"sync skills\", \"refresh from upstream\", \"pull matt's skills\", \"sync upstream\", \"update the transplanted skills\", or wants to bring the owned transplanted skills up to date. Do not use to add or rename a transplanted skill — that stays manual.","https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers\u002Fblob\u002Fmain\u002Fskills\u002Fsync-upstream-skills\u002FSKILL.md",[],[103,104,105,106,107,29,108,109,110,111,112,113,114,115,116,117,32,118,93,119,120,121],"sync upstream","sync upstream skills","upstream","sync","refresh","references","pull","duplicate skill","duplicate","frontmatter","native","changes","up","when","re","bring","content","use","git",{"skillNo":123,"name":124,"nameZh":6,"description":125,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":126,"categories":127,"keywords":128,"publishedAt":33},"01M6CVKNEPEY1WA7E5FWQ85NJW","repograde","Grade student repositories with optional date filtering in single-repo or bulk mode. Use when the user says \"repograde\", \"grade the repos\", \"grade this repo since \u003Cdate>\", \"bewerte die Abgaben\", \"korrigiere die Repos\", or asks for a repo grading run over a class folder or a single student repository. Do not use for project grading with issues and PRs (use projectgrade) or for knowledge-check exams (use knowledge-assessment).","https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers\u002Fblob\u002Fmain\u002Fskills\u002Frepograde\u002FSKILL.md",[],[124,29,129,130,131,132,133,134,31,135,136,137],"with","repositories","single","student","mode","bulk","execution","repo","filtering",{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":144,"publishedAt":33},"01M6CVKF9MVFPHBH3W2D5ETEB6","projectgrade","Grade student project repositories based on Git commits, GitHub Issues, and Pull Requests. Use when the user says \"projectgrade\", \"grade the project\", \"bewerte das Projekt\", \"Projektarbeit benoten\", or asks for a holistic grading of a collaborative repository from inside it, based on commits, issues, reviews and PRs. Do not use for per-lesson assignments (use repograde) or for knowledge-check exams (use knowledge-assessment).","https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers\u002Fblob\u002Fmain\u002Fskills\u002Fprojectgrade\u002FSKILL.md",[],[140,145,146,130,147,132,148,31,121,149,29,150,135,151,152,109],"github issues","requests","git commits","project","github","pull requests","issues","commits",{"skillNo":154,"name":155,"nameZh":6,"description":156,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":157,"categories":158,"keywords":159,"publishedAt":33},"01M6CVKA8J284DZTBE7QRN45SG","lehrplan","Richtet österreichische HTL-Unterrichts-Repositorien ein und pflegt sie — drei Kernaufgaben: das gesetzliche Lehrplan-Material eines Ausbildungszweigs (z. B. WII, WIT, KIF) aus dem RIS beschaffen inklusive jährlichem Novellen-Check, den Lehrplan eines konkreten Gegenstands aus dem Gesetz extrahieren (mit Erläuterung pro Lernziel: was der Inhalt tatsächlich IST, plus praktische Anwendungen), und Semesterpläne interaktiv entwerfen. Use when der Nutzer 'Lehrplan', 'Unterrichtsvorbereitung prüfen', 'Lehrplan auf Novellen prüfen', 'RIS sync', 'Gesetzesmaterial holen', 'Lehrplan extrahieren', 'Jahrespläne neu ziehen', 'Erläuterungen ergänzen', 'Semesterplan erstellen\u002Füberarbeiten' oder 'Einheiten erstellen' sagt, oder wenn in einem Repo gearbeitet wird, das der lehrplan\u002F-Konvention folgt. Do not use for Prepared Lessons (create-lesson), Aufgaben im Kohortenordner (homework) oder Wissensüberprüfungen (knowledge-exam).","https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers\u002Fblob\u002Fmain\u002Fskills\u002Flehrplan\u002FSKILL.md",[],[155,160,161,162,106,163,31,136,29,164],"material","pro","curriculum","check","drei",{"skillNo":166,"name":167,"nameZh":6,"description":168,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":169,"categories":170,"keywords":176,"publishedAt":33},"01M6CVK6QMR16MSMS2MRG097GV","knowledge-persistence","Persist session context into the structured docs\u002Fai knowledge files (OKF bundle; committed and pushed at the end). Use when the user says \"remember\", \"merk dir das\", \"save context\", \"persist knowledge\" or \"update knowledge file\", at a session checkpoint, or when an observation in this session contradicts docs\u002Fai\u002F or AGENTS.md. Do not use for reading the knowledge files at session start — that is plain bootstrapping.","https:\u002F\u002Fgithub.com\u002Fgeorgernstgraf\u002Fopencode-helpers\u002Fblob\u002Fmain\u002Fskills\u002Fknowledge-persistence\u002FSKILL.md",[171],{"categoryNo":172,"categoryName":173,"taxonomyType":174,"taxonomyTypeDesc":175},"01JBSEEDCAT000000000000021","资料检索与研究","SCENARIO","使用场景",[177,178,179,180,181,182,183,29,184,185,186,187,188],"knowledge persistence","knowledge","persistence","persist","structured","context","docs","documentation","workflow","files","session context","session"]