AI reference

The framework's AI paths, collated into one reference. The shell's claude-solve command runs Claude Code headless against a single problem's solution files (the claude-euler-solver skill), and claude-blog runs it against one blog article under blogs/ (the claude-euler-blogger skill); both skills are below. claude-api generates solution artifacts — Python and C code, notes.html, test_cases.json — through the Claude API from the prompt templates further down. All of them are held to the shared convention_* guides listed in the docs index.


The claude-euler-blogger skill

Claude Euler Blogger (headless)

A skill that runs headless to write one blog page under blogs/. The claude-blog shell command launches Claude with:

claude -p /claude-euler-blogger <tag-or-blog> [additional_prompt]

A blog collects the problems that share a tag and explains the idea behind them. Each tag has a page at blogs/<facet>/<slug>.md; curated cross-cutting blogs live elsewhere under blogs/ (e.g. blogs/number-theory/primes.md). Claude researches the covered problems, writes the page, commits it, refreshes its Problems section, and ends the turn. The commit stays local: the collaborator reviews and amends the work, then pushes it themselves.

Read first — the conventions

The tag graph and the house voice are the two things to honour:

  • docs/convention_tags.md — the tag system: the three facets (domain / technique / takeaway), and how a page declares the tags it covers.
  • docs/convention_documentation.md — the writing voice used in notes.html: a "Programming Techniques" course for engineers, first person, precise, why not just what. A blog page carries that same voice, one level up: it explains the idea across many problems, not one.

The central vocabulary is blogs/tags.json (each tag's name, reference URL, and refs — the problems/solutions that carry it). Read the exact solver command usage in docs/commands-index.md — especially blog, blogs, and update-tags.

Rule — reference private problems, but never hand out their answer

Problems > 100 live under solutions/private/ and are encrypted at rest, because Project Euler asks that solutions to problems past 100 not be published. Blog pages under blogs/ are plaintext and public. The boundary that follows is about the answer, not the problem:

  • Do name a private problem by number, summarise its statement, and explain the shared idea and the approach in your own words — that is the point of a blog page, and a page that ducks every private problem is a worse page.
  • Do include a small illustrative snippet only if it cannot compute the answer — a two-line sketch of the general technique, a formula, a shape of a loop. If someone could paste it and get the number, it is too much.
  • Never print a private problem's final answer, and never paste its full working solution code or its notes.html verbatim. For anything you want to quote at length, use a public problem (solutions/public/pNNNN/), whose code is already published.

When in doubt, describe the method and link the problem; the reader can open the solution themselves if they are entitled to it.

Execution model

Runs at the repository root and edits the one target page in place. solver is on PATH; run subcommands directly (solver "blog technique/sieve-of-eratosthenes"). These Bash commands run without confirmation; the skill also relies on Edit/Write under blogs/** and Read on the project tree — keep .claude/settings.local.json in sync:

- bash solver *
- bash git add:* / git commit:*
- edit/write blogs/**      # the article
- read ./**

Do not change cwd; every command runs from the project root, no ./ prefix.


Phase 1 — Resolve the target

The argument is one of: a <facet>/<slug> path, a bare tag slug, or a curated blog path.

  1. Run solver "update-tags" first. It regenerates every page's Problems section from the current graph — so the list you write around is fresh, with each problem's title and a solved/unsolved marker baked in as of now. (You will run it again at the end.)
  2. Resolve the argument to a file:
    • a <facet>/<slug> path → blogs/<facet>/<slug>.md;
    • a bare slug → look it up in blogs/tags.json, take its facet, target blogs/<facet>/<slug>.md;
    • any other path → blogs/<path>.md.
  3. Run solver "blog <path-without-.md>" to see the tag(s) the page declares and the problems/solutions on their central legs.
  4. Read the target file — after step 0 it always exists. It is usually a skeleton: a <!-- tags: [...] --> comment on line 1, a <!-- status: draft --> comment under it, an # H1 title, a _TODO: …_ stub, and a generated Problems section delimited by <!-- problems (generated by update-tags) --> … <!-- /problems -->. That section is machine-owned and rendered — it is the reader's list of the covered problems. You replace the _TODO_ stub with prose; leave the tags comment, the status comment, and the whole Problems section exactly as they are — update-tags owns them, and the save gate refuses an edit that changes them.

The status is the page's state of writing: draft while it is a stub or half-written, final once it is a finished article. A final status also carries the time the article was written (<!-- status: final 2026-10-14T08:32Z -->), which update-tags stamps — see Phase 4. blogs/articles.json (the article index, maintained by update-tags) mirrors both — the time as the row's written. The claude-blog command refuses a final page unless it was run with --force, so if you were launched on one, the maintainer asked for a rewrite deliberately.

If the [additional_prompt] gave an angle or constraint, factor it in without overriding the conventions.

Phase 2 — Research the idea

  1. Read the tag's reference URL and name from blogs/tags.json to pin the concept.
  2. From the problems on the tag's legs, read a representative handful — their statement.html, notes.html (Problem Analysis + Solution Approach), and, the solution code — to see how the idea is actually used and where it varies.
  3. Note the through-line: what the idea is, why it recurs across these problems, the shape it takes, and when an engineer should reach for it.

Phase 3 — Write the page

Replace the _TODO_ stub (or, for a new curated blog, create the file) with a focused article:

  • Lead — one short paragraph: what this domain / technique / takeaway is, and why it keeps coming back across the problems below.
  • The idea — the shared structure or method, explained once, well. Define terms; link the central reference and other authoritative sources (Wikipedia, official docs, OEIS, MathWorld) inline as normal Markdown links.
  • How to reason about it — when it applies, what it buys, the trade-offs and pitfalls.

Do not write a problems list. The generated Problems section already lists every covered problem, with links, titles and solved markers — that is what replaces the old hand-picked "in the wild" section. Write your prose above that section; you may refer to specific problems by number in the flow of the idea (a public one when you want to quote code — see the private-problem rule), but do not duplicate the list.

Keep it tight — a blog page, not an essay. Leave the <!-- tags: [...] --> comment, the <!-- status: … --> comment (until Phase 4 marks it final), and the whole <!-- problems … --> … <!-- /problems --> section exactly as they are — update-tags owns them. Use GitHub-flavoured Markdown (## headings, tables, code fences); do not put raw HTML in the page (it renders to every reader and the save gate refuses it). A new curated page must open with its own <!-- tags: [slug, …] --> comment, carry a <!-- status: draft --> comment under it, and include an empty <!-- problems (generated by update-tags) --> / <!-- /problems --> pair for update-tags to fill.

Phase 4 — Finalize (always last)

Writing the page is not delivering it. The four steps run in order — each is worth nothing without the one before it — and any of them failing is a stop-and-report:

  1. Mark the page final — undated. The article is written, so change its status comment to exactly

    <!-- status: final -->
    

    with no timestamp, even on a --force rewrite of a page that already carries one (<!-- status: final 2026-08-03T14:12Z --> → <!-- status: final -->). The refresh in step 3 stamps the current UTC time on it; that time is compared with each covered problem's solve time (solutions/problems.json) to find articles written before some of their problems were solved, so it must be the time of this writing — a stale timestamp left in place would hide exactly the problems you just drew on. Do not write a time yourself.

    Do this only for a page you actually finished — if you stopped short (missing research, an unresolved update-tags issue, a half-written section), leave it draft, say so in the summary, and stop. final is a claim that the page needs no more writing.

  2. Commit your page — before the refresh, not after:

    solver "git-commit blogs message='docs(blog): <name>'"
    

    git-commit stages what its targets name, and blogs is both legs of the tag graph: your page and blogs/tags.json / blogs/articles.json, and each problem's tags.json, which update-tags rewrites when it accepts a proposed tag. One target covers the whole graph, and nothing outside those paths can ride along. Blog pages carry no solutions, so the message needs no secrecy: just say what was written, tagged docs(blog): so the log reads at a glance.

    Order matters. update-tags commits its own output, so running it first would sweep your article into a generic chore(update-tags): commit and your docs(blog): message would never land. Commit first; let the refresh commit only what it itself changes.

    Pass the message explicitly, as above. With no message= the command asks for one at the prompt, and headless there is nobody to ask — it exits 2 without committing.

    Do not run solver "commit" bare: with no target it is problem-scoped — it stages a solutions/pNNNN directory plus problems.json, not a blog page, so it would not commit your work at all.

  3. Refresh the Problems section: solver "update-tags" — it rewrites every article's Problems section (fresh titles and solved markers), stamps your final with the current time, reconciles the graph, and updates blogs/articles.json (where your new status lands), committing whatever it changed. Then solver "update-tags --check" must exit 0; fix any issue it names (an unknown tag in your <!-- tags: --> comment, most likely) and re-run.

  4. Sanity-check: solver "blog <path>" lists the tags and problems the page now resolves to.

The phase ends at the commit. Pushing the branch and opening its pull request is the collaborator's step (solver "git-push"), taken once they have read the page and amended anything they want changed — so do not push, and never merge anything yourself.

Then summarise in one or two sentences (which page, and the idea you drew out) and end the turn.


Notes & failure modes to avoid

  • Write about the idea across the problems, not a recap of any single solution — that is what notes.html is for.
  • Never print a private (> 100) problem's answer or paste its full solution code / notes; name it, summarise, and take any quotable code from a public problem (see the private-problem rule).
  • Do not hand-edit the <!-- problems … --> section or the <!-- tags: --> comment's membership to force a problem in or out — fix the tags at their source (a problem's tags.json) if the mapping is wrong, then update-tags. The save gate refuses edits to those regions anyway.
  • A non-zero update-tags --check, or a commit failure, is a stop-and-report, not a reason to push on.

The claude-euler-solver skill

Claude Euler Solver (headless)

A skill that runs headless against a single problem's solution files. The claude-skill shell command launches Claude with:

claude -p /claude-euler-solver <problem_number> <action> [additional_prompt]

<problem_number> selects the problem, <action> is solve or review, and [additional_prompt] is optional free-form guidance to factor into that action. Claude does the work, prints a short summary, and ends the turn, returning control to the caller.

Read first — the conventions

The standards each action must meet live in the convention_*.md guides under docs/ (each is also injected into the matching generation prompt — they are the single source of truth). All paths are relative to the project root, which is the working directory.

For exact solver command usage (aliases, flags, arguments), see docs/commands-index.md — e.g. ls, new, evaluate/eval, benchmark, lint, mark and git-commit. The solution interface (@runner.main / runner.h) is documented in docs/solver-guide.md.

Execution model

The skill runs at the repository root and operates directly on the problem's solution files. solver is on PATH; run subcommands directly and pass the problem number (solver "ls 42", solver "eval 42 lang=py", …). Every command takes an optional leading problem number and otherwise defaults to the last one used, so once a step names the problem the rest can omit it. Finishing simply means ending the turn.

Every problem's files live in its own solution directory:

  • problems ≤ 100: solutions/public/pNNNN/ — plaintext.
  • problems > 100: the private solution tree — transparently encrypted at rest, plaintext in the working tree (the git clean/smudge filter handles it), so you read and edit them like any other file.

Run solver "ls <n>" to print the exact file paths and sizes for the problem; call that directory the solution directory below (e.g. solutions/public/p0007/). A solution runs directly from there, e.g. solutions/public/p0007/p0007_s0.py <arg>... --runs=1 [--show] — the last output line is <runs> <average> <result>.

Permissions and paths

These Bash commands run without user confirmation; the skill also relies on Edit/Write on the solution directory and Read on the project tree (./**). These must be granted in .claude/settings.local.json — keep the two in sync.

- bash autoflake *
- bash autopep8 *
- bash black *
- bash echo:*
- bash env
- bash flake8 *
- bash gcc *
- bash isort *
- bash ls:*
- bash mypy *
- bash pip:*
- bash python:*
- bash scripts/c/compile.sh:*
- bash scripts/git/status.sh:*
- bash scripts/linters/check.sh:*
- bash solver *
- bash stat:*
- bash solutions/**            # run a solution binary/script from its directory
- edit/write solutions/**      # create and edit solution files

Path convention. Every command runs from the project root. No command is prefixed with ./ — the allowlisted paths all contain a slash, so bash runs them relative to the cwd; PATH-resolved tools (solver, flake8, gcc, python, …) take no path at all; a solution file runs as its solver ls-reported path (solutions/public/pNNNN/pNNNN_sK…, no ./). Keep each invocation in this form so it matches the allowlist — a stray ./ or cd misses the pattern and is auto-denied in a headless run.


Phase 1 — Start

The problem number is the first argument. Locate its files and confirm they exist:

solver "ls <problem_number>"

This lists the solution directory (e.g. solutions/public/p0042/…). Confirm statement.html is present and read it (plus any data file it references from the directory's resources/). If the solution directory has no statement.html, abort and report that the problem is not initialised — the shell's claude-skill command is expected to hand this skill an existing problem.


Phase 2 — Action

Dispatch on <action>. If an [additional_prompt] was given, treat it as extra caller guidance — an algorithm hint, a constraint, an aspect to focus on, or an additional step to perform — and factor it in without overriding the action's contract or the conventions.

Note: implement any required analysis/brute-force scripts in the solution directory as non-executable files. The eval / benchmark harness only discovers executable pNNNN_sK… files, so keeping these scratch scripts non-executable leaves them out of the run set; preserve them (do not delete them) as a record of the working.

solve

Write and verify a Python solution, translate it to C, then document and summarise:

  1. Understand the problem. Read the solution directory's statement.html and any data file it references from resources/.
  2. Test cases. If test_cases.json is missing, create it with solver "new <n> --tc"; then make it correct and complete per convention_test_cases.md — every dev case has a non-null answer, exactly one main case, extra optional.
  3. Implement solve(). Add a Python file with solver "new <n> --py" if needed, then implement it per convention_python_solution.md (build sieves/caches inside solve() for benchmark fidelity; never print after the harness line or rewrite the scaffolding). Test directly: <solution_dir>/pNNNN_sK.py <arg>... --runs=1 [--show] — the last output line is <runs> <average> <result>, and each dev result must equal its answer. Brute-force cross-check small inputs where possible. Once it is right, run the main case and record its (sanity-checked) answer into test_cases.json; do the same for any extra case that is feasible to run.
  4. Verify Python. solver "eval <n> lang=py solution_index=K" (add all to include extra) until the verdict is correct.
  5. Translate to C. solver "new <n> --c" adds a .c sibling for each .py. Implement it per convention_c_translation.md — the same algorithm as the Python. Compile with scripts/c/compile.sh <solution_dir>/pNNNN_sK.c, then verify with solver "eval <n> lang=c solution_index=K" until correct. If a faithful C port is impractical, leave the template in place with a remark explaining why.
  6. Document. Replace the template's placeholder module and function docstrings with the real approach. Bring the in-source docs to convention_source_documentation.md.
  7. Summarise. Run solver "benchmark <n> && benchmark <n>" to record fresh timings (benchmark twice), then write/refresh the solution directory's notes.html per convention_documentation.md, using the recorded results.json values for every timing claim. notes.html is Problem Analysis and Solution Approach only — no techniques or takeaways sections.
  8. Tag. Write/refresh the solution directory's tags.json per convention_tags.md: the problem's domain, each solution index's techniques, and any takeaways — choosing slugs from the vocabulary in blogs/tags.json and proposing anything genuinely new under new-tags. Write the file only; reconciling it into the central vocabulary is Finalize's job, after mark, so the articles it regenerates carry this problem's solved marker.

review

Audit an existing solution and bring it up to standard. Precondition: at least one Python solution file (pNNNN_sK.py) must exist — if none does, stop and report that this is a solve job, not a review. Then, in order:

  1. C ↔ Python parity. For each solution index, read the .py and its .c and judge whether the C implements the same algorithm (same approach, complexity, and data structures in spirit — not line-for-line). Find unimplemented/broken C with solver "eval <n> lang=c" (a (no solve) or incorrect verdict). Where the C is missing or has diverged, fix it per convention_c_translation.md: solver "new <n> --c" to add any missing sibling, implement, compile (scripts/c/compile.sh <solution_dir>/pNNNN_sK.c), and verify with solver "eval <n> lang=c solution_index=K" until correct. Leave a .c that already matches its Python untouched, and do not change any algorithm.
  2. In-source documentation. Read each pNNNN_sK.py / .c and bring its module and function docstrings and comments up to convention_source_documentation.md. This is a documentation pass only — do not change algorithm or behaviour. A solve() left with the template placeholder (or no docstring) is below the floor; add the missing approach-and-complexity note.
  3. notes.html. Ensure results.json is present and newer than every solution source (solver "ls <n>" shows the mtimes); if it is missing or stale, run solver "benchmark <n> && benchmark <n>" to refresh it (benchmark twice). Then bring the solution directory's notes.html to convention_documentation.md, using the recorded results.json values for every timing or comparison claim — never invent numbers. It must be Problem Analysis and Solution Approach only; if the notes still carry Programming Techniques or Key Takeaways sections, remove them (that content is now carried by tags).
  4. tags.json. Bring the solution directory's tags.json to convention_tags.md: confirm the domain fits the problem, each solution index's techniques reflect that index's code, and takeaways are genuinely transferable — choosing slugs from blogs/tags.json and proposing new ones only under new-tags. Write the file only; Finalize reconciles it.

Phase 3 — Finalize (always last)

Files are edited in place in the solution directory, so finishing means leaving the work in a state a collaborator can review: make it clean, record it as solved, reconcile the tag graph, and commit. The four steps run in order — each one is worth nothing without the one before it — and any of them failing is a stop-and-report:

  1. Lint and fix: solver "lint <n> --auto-fix". It clears the mechanical flake8 issues automatically; fix any remaining mypy/flake8 errors by hand and re-run until lint(...) → 0. If you cannot make it pass, stop and report; do not paper over it.

  2. Mark it solved: solver "mark". It records the problem as solved only once the recorded results confirm it — there must be a main test case with an answer and a correct verdict for it in results.json. A problem already marked is left unchanged (still exit 0). If it refuses, run solver "benchmark <n>" to record fresh results and try again; if it still refuses, stop and report — an unconfirmed solution is not finished work.

  3. Reconcile the tag graph: solver "update-tags". It promotes any new-tags you proposed into the central vocabulary, rebuilds every refs leg from the per-problem files, and regenerates the blog articles and blogs/articles.json.

    After mark, and before the commit. After, because the articles it regenerates carry each problem's solved marker — run it first and this problem still reads unsolved. Before, because update-tags commits its own output (blogs/ and every problem's tags.json, both legs together and nothing else), so letting it go first means step 4 has only your solution left to stage.

    Then solver "update-tags --check" must exit 0; fix whatever it names — an unknown slug or an unpromoted proposal in this problem's tags.json is the likely cause — and re-run.

  4. Commit: solver "git-commit solution message='<message>'".

    One target: solution stages this problem's directory plus solutions/problems.json (what step 2 wrote). The tag graph needs no target here — step 3 already committed both of its legs, this problem's tags.json included.

    The message is a named argument (message=…), not a positional one; pass it explicitly. Headless there is nobody to ask, so git-commit with no message= exits 2 without committing. Quote it as shown when it contains spaces.

    Keep <message> short — a single line that does not reveal the solution approach. Use a word or two from the problem title and make it unique with a bit of personality, e.g. solver "git-commit solution message='p0042 de-coded triangles with a smile'", solver "git-commit solution message='p0042 coded triangles bites the dust'", etc. Never mention the algorithm, formula, or any hint of how it was solved.

The phase ends at the commit. Pushing the branch and opening its pull request is the collaborator's step (solver "git-push"), taken once they have reviewed the work and amended anything they want changed — so do not push, and never merge anything yourself.

Then summarise the session in one or two sentences (the action, and what was found or done) and end the turn.


Notes & failure modes to avoid

  • Do not change cwd; the allowlist assumes the project root is the cwd.
  • Work on the problem you were handed — its files live in the solution directory solver "ls <n>" reports; do not touch other problems.
  • In solve: code against the actual test_cases.json input schema; verify dev before computing main; build caches inside solve(); never print after the harness's final line; and write the computed answer into every null answer.
  • In solve: create any required analysis/brute-force artifacts as non-executable files in the solution directory so the harness ignores them.
  • In review: do not rewrite a .c that already matches its Python, and do not change any algorithm; keep .c files non-executable (only the compiled _c is +x). Take only the corrective steps the audit actually requires — a solution that is already in parity, documented, and summarised is a valid "nothing to do".
  • In Finalize: a non-zero lint exit, or an unfixable lint error, is a stop-and-report, never a reason to push on. The same goes for the three steps after it — a refused mark, a non-zero update-tags --check, or a failed commit stop the phase and get reported as they came back. Never work around one: no --force, no hand-edited problems.json, no hand-edited blogs/tags.json, no pushing or merging on the collaborator's behalf.

prompt_c.txt

You are a master C programmer translating Python Project Euler solutions to C.

## Problem
Number : $number
Title  : $title
Level  : $difficulty
Solved : $solved_date

## Problem statement (HTML)
$problem_content

## Existing Solution Notes (HTML)
$solution_notes

## Existing Solution files
$file_naming

$solutions

## Test cases
$test_cases

## Current benchmark results
$results

## Solution Template
Use the following C template as the structural basis for each generated file:

$solution_template

---

Generate only the files listed below. Each corresponds to the Python solution with the same index.
Use the existing solution files above for context; do not regenerate files that already exist.

Files to generate: $files_to_generate

Follow the C translation conventions below in full:

$c_translation_conventions

Apply these source documentation conventions to the in-source comments:

$source_documentation_conventions

Generation-task requirements (in addition to the conventions above):

1. Each file to generate corresponds to the Python solution with the same index; use the existing
   solution files above for context and do not regenerate files that already exist.

2. Begin each file with the comment: /* Solution to Euler Problem $number: $title. */

3. Skip matplotlib and any graphical visualization code entirely.

4. If a Python solution depends on libraries with no reasonable C equivalent (anything beyond
   numpy / primesieve / GMP / MPFR / OpenSSL BIGNUM, e.g. sympy or networkx), do not fabricate a
   port: emit a one-line file containing only `// SKIP: <one-sentence reason>` and move to the next.

5. $house_style

6. Output each C file wrapped in delimiter lines, one after another, no other text:

// BEGIN <filename>
[c source]
// END <filename>

prompt_doc.txt

You are a meticulous software documentarian improving the in-source documentation of Project Euler solutions, written in Python and C.

## Problem
Number : $number
Title  : $title
Level  : $difficulty
Solved : $solved_date

## Problem statement (HTML)
$problem_content

## Existing Solution Notes (HTML)
$solution_notes

## Existing Solution files
$file_naming

$solutions

## Test cases
$test_cases

## Current benchmark results
$results

---

Re-document each of the solution files listed below in place. These files already exist above; this
is a documentation-only pass over the in-source docstrings and comments - do NOT change the
algorithm, behaviour, or any computed value.

Files to document: $files_to_generate

Apply these source documentation conventions in full:

$source_documentation_conventions

Generation-task requirements (in addition to the conventions above):

1. Re-emit every file listed in "Files to document" in full, exactly as it appears above except for
   improved in-source documentation: the module docstring / top comment, the `solve()`
   docstring/comment, and a one-line docstring/comment per non-trivial helper. Do not add, remove,
   reorder, or otherwise alter any executable code.

2. Leave the runner scaffolding byte-for-byte untouched. For Python keep the
   `#!/usr/bin/env python3.14` shebang, the `# -*- coding: utf-8 -*-` line, the
   `from __future__ import annotations`, the `from solver.runners import runner` import, the
   `@runner.main` decorator, and the `if __name__ == "__main__"` block exactly as above; for C keep
   the `#include "runner.h"` and the `const char *solve(int argc, char *argv[])` signature.

3. Documenting must not change what the program does - each re-documented file must still compile
   (C) and produce the same answers for the test cases above.

4. Keep every line at most 120 columns wide, including the docstrings and comments you add or
   rewrite; the committed solutions are linted with `flake8 --max-line-length 120`, so a longer line
   fails the check. Wrap long prose across multiple comment/docstring lines rather than overflowing.

5. $house_style

6. Output each file wrapped in delimiter lines, one after another, no other text. Between the
   delimiters put the raw source only - do NOT wrap it in a markdown code fence (no ```c, ```python,
   ``` or ~~~ lines); a fence leaks stray backticks into the file and breaks compilation:

// BEGIN <filename>
[source]
// END <filename>

prompt_notes.txt

You are a master programmer and educator writing worked-solution notes for a "Programming Techniques" course.
Your audience is engineers who are learning to write efficient, well-reasoned programs.
Write with clarity, precision, and authority - in the first person ("I chose...", "My approach...", "I decided to...").
Explain not only *what* you did but *why* - every algorithmic and implementation decision should be justified
so that a person reading these notes can learn to reason the same way.

## Problem
Number : $number
Title  : $title
Level  : $difficulty
Solved : $solved_date

## Problem statement (HTML)
$problem_content

## Existing Solution Notes (HTML)
This section contains previously generated notes for this problem. It may be empty if notes are being
generated for the first time. If it is not empty, the notes are being regenerated - typically because
new solution files have been added or existing ones changed. Use these notes as a reference, but
write fresh notes that accurately reflect all current solutions and benchmark results below.

$solution_notes

## Solution files
$file_naming

$solutions

## Test cases
$test_cases

## Benchmark results
$results

---

Write the notes.html article following these documentation conventions in full:

$documentation_conventions

Base every timing or comparison claim on the "Benchmark results" above (the recorded `average`
values and verdicts) - never invent numbers. Output the HTML only - no preamble.
$house_style

prompt_py.txt

You are a master Python programmer writing Python solutions to Project Euler problems.

## Problem
Number : $number
Title  : $title
Level  : $difficulty
Solved : $solved_date

## Problem statement (HTML)
$problem_content

## Solution Notes (HTML)
$solution_notes

## Existing Solution files
$file_naming

If no solutions exist, write the first one. If solutions exist, implement a different algorithm.

$solutions

## Test cases
$test_cases

## Current benchmark results
$results

## Solution Template
Use the following Python template as the structural basis for the generated file:

$solution_template

---

Generate only the file listed below, using the existing solutions above only for context.

File to generate: $file_to_generate

Follow the Python solution conventions below in full:

$python_solution_conventions

Apply these source documentation conventions to the in-source docstrings and comments:

$source_documentation_conventions

Generation-task requirements (in addition to the conventions above):

1. The first line must be exactly: `#!/usr/bin/env python3.14`. Keep the template's harness
   scaffolding - the shebang, `# -*- coding: utf-8 -*-`, `from __future__ import annotations`, the
   `from solver.runners import runner` import, the `@runner.main` decorator, and the
   `if __name__ == "__main__": raise SystemExit(solve())` block - byte-for-byte; implement only
   `solve()` and the module-level helpers above it.

2. If existing solutions are present, implement a genuinely different algorithm - do not replicate
   any existing approach.

3. $house_style

4. Output the Python file wrapped in delimiter lines, no other text:

// BEGIN <filename>
[py source]
// END <filename>

prompt_tags.txt

You are a meticulous librarian tagging a Project Euler solution for a technique index.
You read the problem and every solution, then record the problem's domain, each solution index's
programming techniques, and any transferable takeaways as tags - choosing from an existing
vocabulary and proposing a new tag only when nothing fits.

Everything below - the conventions, the vocabulary and the output contract - is fixed. The single
problem to tag arrives in the message that follows.

$tag_conventions

### Tag vocabulary (choose by slug; propose new only when nothing fits)
$vocabulary

### Output contract

Emit the problem's `tags.json` following the tag conventions above in full.

Output the JSON object only - no markdown, no code fences, no prose. It must start with `{` and
end with `}`, and contain exactly the keys `domain`, `takeaways`, `techniques`, and `new-tags`.
The `techniques` map must have one key per solution index (`s0`, `s1`, …) that the message
provides, each listing only the techniques *that index* uses. Prefer existing vocabulary slugs;
put any genuinely new tag's full definition in `new-tags`, never a bare slug in a facet list.

**A slug's facet is fixed by the vocabulary, and it is the whole slug's identity.** Every entry
above is marked `[domain]`, `[technique]` or `[takeaway]`. A slug marked `[technique]` may appear
only inside `techniques`; one marked `[domain]` only in `domain`; one marked `[takeaway]` only in
`takeaways`. Putting a slug in any other list is rejected outright - so if a slug fits what you
want to say but sits in the wrong facet, do not file it there and do not re-propose it under the
facet you wanted. Slugs are globally unique across facets: never propose a `new-tags` slug that
already appears above, whatever its facet. Drop the tag, or pick a different, unused slug.
$house_style
<!--user-->
## Problem
Number : $number
Title  : $title
Level  : $difficulty

## Problem statement (HTML)
$problem_content

## Solution files
$file_naming

$solutions

## Solution notes (HTML)
$solution_notes

## Test cases
$test_cases

## Benchmark results
$results

prompt_tags_domain.txt

You are a meticulous librarian filing an *unsolved* Project Euler problem into a subject index.
Nobody has written a solution yet, so there is no code to read: you classify the problem purely on
what it is *about* - the mathematical and computational subject matter its statement puts in play -
choosing from an existing vocabulary and proposing a new tag only when nothing fits.

Everything below - the conventions, the vocabulary and the output contract - is fixed. The single
problem to tag arrives in the message that follows.

$tag_conventions

### Domain vocabulary (choose by slug; propose new only when nothing fits)
$vocabulary

### Output contract

Emit the problem's `tags.json` following the tag conventions above, with one exception that
follows from there being no solution: **assign `domain` tags only**.

- `domain` - the subject matter, from the vocabulary above. Two to four slugs is typical; pick the
  ones a reader searching for this blog would actually use, not every field the problem touches.
- `takeaways` - always the empty list `[]`. A takeaway is what a *solution* teaches, and there is
  no solution yet.
- `techniques` - always the empty object `{}`. Techniques attach to a solution index, and there
  are no solution indices yet.
- `new-tags` - a full tag definition only when the problem's subject genuinely has no slug above.
  Prefer an existing slug: a near-fit that a reader would find beats a perfect one nobody searches
  for. Anything you do propose must be facet `domain`. Slugs are globally unique across the whole
  vocabulary, of which only the `domain` facet is shown above - so a slug you invent may already
  exist as a technique or takeaway. Keep proposals specific to the subject matter (`digit-sum`,
  `amicable-numbers`) and never propose a bare restatement of the problem title.

Judge only from the statement. Do not guess how the problem would be solved, and do not tag an
algorithm or data structure you imagine a solver might reach for - that is a technique, and
techniques are out of scope here.

Output the JSON object only - no markdown, no code fences, no prose. It must start with `{` and
end with `}`, and contain exactly the keys `domain`, `takeaways`, `techniques`, and `new-tags`.
$house_style
<!--user-->
## Problem
Number : $number
Title  : $title
Level  : $difficulty

## Problem statement (HTML)
$problem_content

prompt_test_cases.txt

You are a master programmer preparing test cases for Project Euler problems.
Your task is to extract and generate test cases from the problem statement and output them as a JSON array.

## Problem
Number : $number
Title  : $title
Level  : $difficulty

## Problem statement (HTML)
$problem_content

## Existing Test Cases
$test_cases

---

Follow these test-case conventions in full:

$test_case_conventions

Additional rule for this task - **existing test cases**: if the "Existing Test Cases" section above
is not empty, always include every existing case in the output, validated or corrected as follows:
- **Input keys**: fix any incorrectly or inconsistently named keys.
- **`dev` answers**: if the case corresponds to a worked example in the problem statement,
  verify the answer against it and correct or fill in the value as needed. If no matching
  example is found in the problem statement, leave the answer unchanged.
- **`main` and `extra` answers**: leave as null (the problem is unsolved).
- Do not add a new case that duplicates an existing one.

### Output format

Output a single JSON array only - no explanation, no markdown, no code fences, no preamble.
$house_style
Each element must conform exactly to this schema:

{
  "category": "dev" | "main" | "extra",
  "input": { "<param_name>": <value>, ... },
  "answer": <number> | <string> | null
}