Contributing to cpp-grail
Repository shape¶
hundred/NN-โฆ/ The Hundred โ curated, difficulty-ordered, CLOSED
gotchas/<slug>/ open collection: traps that bite real code
trivia/<slug>/ open collection: legal, astonishing obscura
templates/ skeletons to copy for new entries
tools/ validators, index generation, and website staging
website/ themes, the quiz page, site CSS and JavaScript
Each entry is a directory holding README.md and main.cpp โ nothing else. A few entries
legitimately carry a second .cpp or an .hpp because the lesson needs two translation
units.
The three collections¶
| Collection | Where | Status |
|---|---|---|
| The Hundred | hundred/NN-<slug>/ |
Frozen. Curated, difficulty-ordered, closed to new entries. |
| Gotchas | gotchas/<slug>/ |
Open. Traps that bite real code. Same contract, no number. |
| Trivia | trivia/<slug>/ |
Open. C++ obscura: true, strange, and mostly useless. |
The Hundred is finished. Its numbers encode difficulty (๐ข 01โ29, ๐ก 30โ69, ๐ด 70โ100),
which only works for a closed set โ inserting an entry would renumber its neighbors and
break every link and cross-reference. So new entries never get a number. They live in
gotchas/ or trivia/ under a slug, and the index orders them instead.
Corrections to an existing entry are always welcome (a wrong claim, a stale compiler message, a typo). Renumbering and reordering are not.
Which type is it?¶
Would you write a "The fix" section with a straight face? Yes โ gotcha. No โ trivia.
A gotcha is a trap. Someone writing ordinary code hits it, loses an afternoon, and there is a concrete thing they should have done instead.
Trivia is obscura. It is legal, it is astonishing, and there is usually nothing to
fix because nobody writes this by accident. Trivia entries state their honest
impact: โ none, rare, or real โ and must cite the standard, because obscura is
exactly where confident wrong claims come from.
If a trivia entry turns out to bite people, it was a gotcha all along. Move it.
Adding an entry¶
The easiest path is to let a coding agent walk you through it. The procedure lives in the repo, so Claude Code, Codex and friends all follow the same steps:
| Task | Claude Code | Any other agent |
|---|---|---|
| Add an entry | /add-gotcha |
follow .claude/skills/add-gotcha/SKILL.md |
| Fix a wrong entry | /report-correction |
follow .claude/skills/report-correction/SKILL.md |
| Add a theme | /add-theme |
follow .claude/skills/add-theme/SKILL.md |
The skill picks the right collection, checks your idea is not already covered, makes you
verify the claim on a real compiler before writing a word of prose, and runs the checker
and the index generator for you. AGENTS.md routes agents that read that
convention instead.
Doing it by hand is fine too โ the skill is just this, written down:
cp -r templates/gotcha gotchas/my-slug # or templates/trivia trivia/my-slug
$EDITOR gotchas/my-slug/main.cpp gotchas/my-slug/README.md
python3 tools/check.py # structure, build, run, documented output
python3 tools/gen_index.py # regenerates README.md
python3 tools/build_site.py # checks website, quiz data and CE links
Then open a PR. Never hand-edit the index tables in README.md โ they are generated, so
editing them by hand guarantees a merge conflict with the next contributor.
Slugs are lowercase words separated by hyphens (erase-remove-idiom). They may not start
with digits โ that shape is reserved for The Hundred.
What an entry contains¶
Two files, and nothing else โ no binaries, no scratch files, no core dumps (ulimit -c 0
for entries that crash on purpose).
main.cppโ the demo. Self-contained, ideally under 35 lines, compiling cleanly underg++ -std=c++17 -Wall -Wextra main.cpp. Extra.cpp/.hppfiles are allowed only when the lesson inherently needs multiple translation units.README.mdโ the write-up, under 70 lines, in the shape below.
README skeleton¶
# NN ยท Title <!-- The Hundred; new entries drop the number -->
๐ข **Beginner** โ <one-line hook that hints at the surprise without spoiling it>
โ```cpp
<the snippet โ same code as main.cpp, possibly trimmed of includes>
โ```
**Run it. What does it print?** <!-- adapt the question to the gotcha -->
<details>
<summary><b>Answer</b></summary>
<the surprising truth, in one or two short lines>
</details>
## Why
<the explanation โ at most 6 sentences, one idea only>
## The fix
โ```cpp
<the corrected line(s)>
โ```
**Takeaway:** <one sentence the reader can memorize>
---
*Try it: `g++ -std=c++17 main.cpp -o demo && ./demo`*
Trivia differs in exactly two places. ## The fix becomes ## Where it shows up โ
there is usually nothing to fix, and pretending otherwise turns a curiosity into a fake
warning; if the honest answer is "nowhere, nobody writes this on purpose", write that.
And the standard must be cited, as a bracket-tag ([expr.sub]) or a cppreference
link. Everything else is identical.
The bar¶
The first hundred entries were each compiled, run, and adversarially fact-checked before landing. New entries meet the same bar:
- It runs. Every claim about output came from actually running the program on a real
compiler. Quote real output and real warning text โ never what you assume it prints.
tools/check.pycompiles the entry, runs it, and requires the answer block to quote exactly what it printed, so a snippet edited without re-running the demo fails. - Undefined behavior is labeled. Say undefined behavior in bold, and present observed output as typical, never guaranteed. "It may even appear to work" is itself part of the lesson.
- The snippet looks innocent. No comment gives the surprise away. The reader should form a confident wrong prediction before scrolling.
- One idea. Related traps get one sentence and a pointer, not a section.
- It is short. README under 70 lines,
main.cpparound 40 or fewer. - It is not already here. Check the index. An entry that restates another entry's rule should instead link to it.
- Trivia cites the standard. A bracket-tag (
[expr.sub]) or a cppreference link.
tools/check.py enforces items 1, 5 and the mechanical half of 7. Items 2, 3, 4 and 6 โ
labelling undefined behavior, keeping the snippet innocent, one idea per entry, and not
restating an entry that already exists โ are what review is for.
Two limits worth knowing. An entry that documents more than one build (the assert
demo quotes both a normal and an -DNDEBUG run) has only its declared build: re-run,
so the other block rests on review. And an answer that quotes no fenced output claims
nothing for the checker to compare against.
Writing rules¶
- Lead with code, not theory. The reader should form a wrong prediction first.
- Mention helpful warnings. If
-Wall -Wextraor a named flag catches the bug, say which one โ knowing what the compiler can catch is part of the lesson. - Compile-error entries keep
main.cppcompiling by default. Guard the broken line with#ifdef SHOW_BUG, tell the reader to tryg++ -DSHOW_BUG โฆ, and quote only the interesting line or two of the error, never the wall of text. - The baseline is C++17. If the behavior changed in another standard, one sentence on what changed and when.
- At most one external link (cppreference), and only when it genuinely adds value.
- Match the neighbors. Follow the formatting the existing entries use rather than imposing your own; an editor-side formatter owns the source style, so do not reformat entries you are not otherwise changing.
STYLE.md in the repository carries the same contract in full.
Entry metadata¶
Every entry in gotchas/ and trivia/ starts its README with a metadata block. The
tooling reads it; GitHub renders it invisible.
<!-- cpp-grail
type: gotcha # gotcha | trivia
difficulty: intermediate # gotchas only: beginner | intermediate | advanced
impact: none # trivia only: none | rare | real
standard: [expr.sub] # required for trivia, optional for gotchas
build: g++ -std=c++17 -Wall -Wextra main.cpp -o demo
expect: run # run | crash | compile-error
showbug: error # optional: a -DSHOW_BUG build must fail
-->
expect: is what lets a demo fail on purpose. crash means the program is supposed to
die by a signal (a double free, a SIGFPE); compile-error means the default build is
supposed to be rejected. Without it, the checker would call your best entries broken.
The website derives each Compiler Explorer link from main.cpp and the metadata build:
flags. Keep that command as a direct g++ invocation with explicit source files and
options. Multi-file lessons are supported on the site, but their Compiler Explorer button
is deliberately labeled as a main.cpp-only view. See WEBSITE.md for local
setup and the generated-content contract.
Verifying your work¶
python3 tools/check.py # validate gotchas/ and trivia/, output included
python3 tools/check.py --all # also smoke-build all 100
python3 tools/gen_index.py # regenerate the index
python3 -m unittest discover -s tools/tests -v
python3 tools/build_site.py && mkdocs build --strict # needs requirements-docs.txt
CI runs these, rejects a stale index, and builds the website before deployment.
.site-src/ and .site-output/ are disposable generated directories; never edit or
commit them.
Adding a theme¶
Entries are not the only thing you can contribute. The site's look is a set of
interchangeable themes, and adding one is a single new file in
website/themes/ โ no other file changes.
Run /add-theme and an agent will walk you through it, or follow
.claude/skills/add-theme/SKILL.md by hand. The
token contract, the rules the build enforces, and a copy-paste skeleton are in
website/themes/README.md.
A theme sets color, type, borders and shadows โ never layout, so switching themes never moves the page. The build rejects a theme that tries.