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 (facet domain / takeaway).
  • techniques — a map from each solution index (s0, s1, …, one key per pNNNN_sK that 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 each techniques list must exist in blogs/tags.json and match its facet there. Do not put a technique slug under domain, 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, or takeaway.
  • 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. takeaway tags have no URL: use null.
  • summary — one sentence.
  • applies-to — for a technique proposal, the solution indices it applies to (["s0", "s2"]); omit for domain / 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.