Tag conventions
These govern a problem's tags.json — the per-problem leg of the tag graph that powers the
blog articles under blogs/. They are distinct from the
Documentation conventions, which govern notes.html.
notes.html explains what the problem is and how each solution works. The programming
techniques a solution uses and the transferable takeaways it teaches are not written as prose
there — they are recorded as tags in tags.json, so they can be collected across problems into
blog pages.
The graph
Every tag is one of three facets:
- domain — what the problem is about: the mathematical or computational subject (number theory, combinatorics, a specific object like a palindrome or a Pythagorean triple). Attaches to the problem.
- technique — how a solution works: an algorithm, data structure, or language/library device (sieve of Eratosthenes, dynamic programming, memoization, a generator, C manual memory). Attaches to a solution index — different indices use different techniques.
- takeaway — a transferable principle the problem teaches ("prefer a closed form over iteration", "a better algorithm beats a faster language"). Attaches to the problem.
The central vocabulary of allowed tags lives in blogs/tags.json; each entry has a slug, a
facet, a human name, and a canonical reference URL. Always choose an existing tag by its
slug. Propose a new one (below) only when nothing in the vocabulary fits.
The file — tags.json
Written as standard json.dumps(indent=2). Four keys:
{
"domain": ["arithmetic-progression", "triangular-number"],
"takeaways": ["closed-form-over-iteration", "algorithm-beats-language"],
"techniques": {
"s0": ["inclusion-exclusion-principle", "closed-form-expression"],
"s1": ["brute-force-search"],
"s2": ["arithmetic-progression"]
},
"new-tags": []
}
domain/takeaways— flat lists of vocabulary slugs (facetdomain/takeaway).techniques— a map from each solution index (s0,s1, …, one key perpNNNN_sKthat exists) to the technique slugs that index actually uses. Read the code per index: index 0's list must reflect index 0's algorithm, not the problem in general. An index that shares an algorithm across Python and C lists it once. An index with no notable technique may have an empty list.- Every slug in
domain,takeaways, and eachtechniqueslist must exist inblogs/tags.jsonand match its facet there. Do not put atechniqueslug underdomain, etc.
Adding a new tag — new-tags
When a solution genuinely needs a tag the vocabulary lacks, add its full definition to
new-tags (do not invent a bare slug in the facet lists). The maintainer's update-tags
command promotes each proposal into blogs/tags.json and files it into the right bucket.
"new-tags": [
{
"slug": "gauss-summation",
"facet": "technique",
"name": "Gauss summation trick",
"reference": "https://en.wikipedia.org/wiki/1_%2B_2_%2B_3_%2B_4_%2B_%E2%8B%AF",
"summary": "Pairing the ends of an arithmetic run to sum it in closed form.",
"applies-to": ["s0"]
}
]
slug— lowercase, hyphen-separated, derived from the canonical name.facet—domain,technique, ortakeaway.reference— an authoritative public URL (Wikipedia, official docs, MathWorld, OEIS). Omit any#bookmark— the base page is the reference — unless the anchor is the identity (a specific symbol on a shared docs page, e.g.functions.html#any), in which case keep the anchor.takeawaytags have no URL: usenull.summary— one sentence.applies-to— for atechniqueproposal, the solution indices it applies to (["s0", "s2"]); omit fordomain/takeaway.
Prefer reusing an existing tag over proposing a near-duplicate; propose sparingly.
Blog articles and the index — blogs/articles.json
Each tag can carry an article at blogs/<facet>/<slug>.md; curated cross-cutting blogs live
elsewhere under blogs/ (e.g. blogs/number-theory/primes.md). Every article opens with two
comments — the tags it covers, and its status:
<!-- tags: [sieve-of-eratosthenes, prime-number] -->
<!-- status: draft -->
# Generating and testing primes
update-tags maintains blogs/articles.json, the article index: one row per writable blog —
every tag in the vocabulary, plus the curated pages — with its title, declared tags, distinct-problem
count and status. Three statuses:
| status | meaning |
|---|---|
missing |
no page on disk yet; the tag is vocabulary only |
draft |
a page exists and is still open for writing (the skeleton stubs start here) |
final |
the article is done |
The article is the source of truth (update-tags stamps a draft comment on any page missing one);
the index only mirrors it. The blogger skill sets final when it finishes writing a page, and the next
update-tags run stamps it with the time it was written: an ISO 8601 UTC timestamp to the minute,
<!-- status: final 2026-10-14T08:32Z -->, which the index mirrors as the row's written (null
for a draft). One fixed-width format in one zone, so it sorts and compares as plain text; set against
each covered problem's solve time (read from problems.json as local time, converted to UTC) it
finds the articles written before some of their problems were solved: each row's solved_after
lists the problems solved at or after its written minute, and the update-tags report names those
articles as rewrite candidates. claude-blog then refuses that blog unless run with --force. The
index also drives claude-blog tab-completion (unwritten and most-referenced first) and the web /blogs grid.
Choosing good tags
- domain: name the subject a reader would search for, not the method. Two or three is typical.
- technique: the algorithms, data structures, and notable language/library devices a given index relies on — the things a "how did this work" reader would want indexed.
- takeaway: only genuinely transferable lessons, phrased as advice — not restatements of the problem. Zero is acceptable; more than three or four is rare.