diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d3c39142..24268c68 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -293,7 +293,9 @@ repos:
args:
- --skip=*.json,*.lock,*-lock.yaml,*.min.js,*.min.css,.git,__pycache__,.venv,*.txt
- --ignore-words-list=als,ans,ect,nd,som,sur,te,nam,numer,lew,sie,wil,postion,clen,ther,folow,derrive,ony,tje,noe,theses,crate,doubleclick,wile,tabel,pary,blok,bloc,proces,serwer,parametr,adres,hart,dout,metod,tekst,synonim,grup,mosty,lokal,skalar,milion,nowe,tre,hel,alph,iif
- exclude: ^(Bash/ffmpeg-build/|LaTeX/|.*\.geojson$)
+ # guide-ladder.html inlines upstream build-your-own-x tutorial titles
+ # verbatim (third-party data, incl. real source typos) — not authored text.
+ exclude: ^(Bash/ffmpeg-build/|LaTeX/|.*\.geojson$|python_pkg/byox_ladder/guide-ladder\.html$)
# ===========================================================================
# DOCFORMATTER - Format docstrings (disabled - causes recursion errors)
diff --git a/docs/superpowers/contracts/byox-ladder-2026-07-17.json b/docs/superpowers/contracts/byox-ladder-2026-07-17.json
new file mode 100644
index 00000000..e0ad80f1
--- /dev/null
+++ b/docs/superpowers/contracts/byox-ladder-2026-07-17.json
@@ -0,0 +1,17 @@
+{
+ "title": "Add byox_ladder difficulty-ladder generator",
+ "objective": "Add a self-contained tool that ranks the codecrafters build-your-own-x projects by difficulty and emits two offline HTML pages: a per-guide ladder (~360 tutorials) and a per-category ladder (30 categories), grouped into Beginner/Intermediate/Advanced/Expert tiers plus an Unsorted bucket.",
+ "acceptance_criteria": [
+ "python_pkg/byox_ladder parses the upstream README into guides.json via parse_guides.py",
+ "build_pages.py emits standalone guide-ladder.html and category-ladder.html that open via file:// with no network",
+ "make build regenerates the whole pipeline (fetch -> parse -> build)",
+ "Tests reach 100% branch coverage on parse_guides.py and build_pages.py",
+ "All pre-commit hooks pass (ruff ALL, mypy, pylint, bandit, codespell, check-python-location)"
+ ],
+ "out_of_scope": [
+ "Vendoring the upstream README (fetched at build time, gitignored)",
+ "Any change to existing python_pkg packages or repo tooling",
+ "A per-guide exact 1..N ranking (difficulty is tier-based by design)"
+ ],
+ "verifier": "pre-commit run --all-files (pre-push stage) and python3 -m pytest python_pkg/byox_ladder/tests"
+}
diff --git a/docs/superpowers/evidence/byox-ladder-2026-07-17.json b/docs/superpowers/evidence/byox-ladder-2026-07-17.json
new file mode 100644
index 00000000..ee4a74dd
--- /dev/null
+++ b/docs/superpowers/evidence/byox-ladder-2026-07-17.json
@@ -0,0 +1,39 @@
+{
+ "intent": "Add python_pkg/byox_ladder: a generator that ranks the build-your-own-x projects by difficulty and produces two offline HTML ladders (per-guide and per-category), committed as runnable pages.",
+ "scope": [
+ "python_pkg/byox_ladder/ (new package: parse_guides.py, build_pages.py, templates, built HTML, tests)",
+ "docs/superpowers/contracts + evidence artifacts for this change",
+ "No changes to existing packages, tooling, or the unrelated working-tree edits (i3 config, feedback-appimage)"
+ ],
+ "changes": [
+ "parse_guides.py parses the upstream README into guides.json; a guide inherits its category tier, language only nudges within-tier order",
+ "build_pages.py inlines guides.json into the guide template and wraps both pages in a standalone HTML skeleton",
+ "Makefile drives fetch -> parse -> build; byox-readme.md and guides.json are gitignored derived artifacts",
+ "tests/ cover both modules to 100% branch coverage"
+ ],
+ "verification": [
+ {
+ "command": "python3 -m pytest python_pkg/byox_ladder/tests --cov=python_pkg.byox_ladder --cov-branch",
+ "result": "pass",
+ "evidence": "21 passed; coverage 100.00% (parse_guides.py 69 stmts/16 branches, build_pages.py 32 stmts/4 branches, __init__ 0) with 0 missing"
+ },
+ {
+ "command": "pre-commit run --files python_pkg/byox_ladder/*.py python_pkg/byox_ladder/tests/*.py",
+ "result": "pass",
+ "evidence": "ruff, ruff-format, mypy, pylint, bandit, codespell, check-python-location, and pytest-coverage all reported Passed"
+ },
+ {
+ "command": "make -C python_pkg/byox_ladder build",
+ "result": "pass",
+ "evidence": "wrote guides.json (359 guides); wrote guide-ladder.html and category-ladder.html, both starting with "
+ }
+ ],
+ "risks": [
+ "Upstream README format drift could break the regex parser; parse_guides.py exits non-zero and the committed HTML stays valid until regenerated",
+ "Difficulty tiers are a subjective judgment; within-tier order is approximate by design"
+ ],
+ "rollback": [
+ "git rm -r python_pkg/byox_ladder and delete the two docs/superpowers artifacts, then commit",
+ "After rollback, run pre-commit run --all-files to confirm the tree is still green"
+ ]
+}
diff --git a/python_pkg/byox_ladder/.gitignore b/python_pkg/byox_ladder/.gitignore
new file mode 100644
index 00000000..cdad6bdd
--- /dev/null
+++ b/python_pkg/byox_ladder/.gitignore
@@ -0,0 +1,4 @@
+# Fetched upstream README (third-party content — not vendored) and the derived
+# dataset. The built *-ladder.html pages ARE committed as the runnable output.
+byox-readme.md
+guides.json
diff --git a/python_pkg/byox_ladder/Makefile b/python_pkg/byox_ladder/Makefile
new file mode 100644
index 00000000..bee3e018
--- /dev/null
+++ b/python_pkg/byox_ladder/Makefile
@@ -0,0 +1,20 @@
+# Regenerate the Build-Your-Own-X difficulty ladders.
+#
+# make build # fetch README -> parse -> build both standalone pages
+# make clean # remove the fetched README copy and derived data
+
+README_URL = https://raw.githubusercontent.com/codecrafters-io/build-your-own-x/master/README.md
+
+.PHONY: build clean
+
+build: guides.json
+ python3 build_pages.py
+
+guides.json: byox-readme.md parse_guides.py
+ python3 parse_guides.py
+
+byox-readme.md:
+ curl -fsSL "$(README_URL)" -o byox-readme.md
+
+clean:
+ rm -f byox-readme.md guides.json
diff --git a/python_pkg/byox_ladder/README.md b/python_pkg/byox_ladder/README.md
new file mode 100644
index 00000000..07bb7168
--- /dev/null
+++ b/python_pkg/byox_ladder/README.md
@@ -0,0 +1,48 @@
+# Build-Your-Own-X Difficulty Ladder
+
+Two self-contained, offline HTML pages that rank the projects from
+[codecrafters-io/build-your-own-x](https://github.com/codecrafters-io/build-your-own-x)
+from easiest to hardest:
+
+- **`guide-ladder.html`** — every individual tutorial (~360 guides), grouped
+ into difficulty tiers and filterable by tier, language, and category.
+- **`category-ladder.html`** — the 30 top-level categories, grouped into
+ Beginner / Intermediate / Advanced / Expert tiers.
+
+Both are single files with all CSS, JS, and data inlined — no server, fonts, or
+network needed. Open either directly in a browser (`file://`) and it works
+offline, in your OS light/dark theme.
+
+## Difficulty model
+
+Difficulty is a judgment call, scored on three axes: prerequisite domain
+knowledge, minimal-version scope, and how much step-by-step guidance exists.
+
+- A guide **inherits its category's tier** — the dominant signal. A Regex-Engine
+ guide is easier than an Operating-System guide whatever the language.
+- **Language only nudges the order _within_ a tier**: lower-level languages
+ (C, Rust, Assembly) sort a little harder than Python or JavaScript. It never
+ moves a guide across a tier boundary.
+- The **Unsorted** group is the repo's own "Uncategorized" bucket. Its guides
+ span every level, so they are deliberately left un-ranked rather than forced
+ into a tier.
+
+The exact head-to-head order inside a tier is therefore approximate by design;
+the tier boundaries are the trustworthy part.
+
+## Regenerating
+
+```sh
+make build # curl the README -> parse_guides.py -> build_pages.py
+make clean # remove the fetched README copy and guides.json
+```
+
+The pipeline:
+
+1. `parse_guides.py` — parses a local copy of the upstream README
+ (`byox-readme.md`) into `guides.json`, applying the difficulty model above.
+2. `build_pages.py` — injects `guides.json` into `templates/guide.template.html`
+ and wraps `templates/category.html`, writing the two standalone pages.
+
+`byox-readme.md` and `guides.json` are derived artifacts and are gitignored; the
+built `*-ladder.html` pages are committed as the runnable output.
diff --git a/python_pkg/byox_ladder/__init__.py b/python_pkg/byox_ladder/__init__.py
new file mode 100644
index 00000000..76eb5fcc
--- /dev/null
+++ b/python_pkg/byox_ladder/__init__.py
@@ -0,0 +1,4 @@
+"""Generate the Build-Your-Own-X difficulty ladder pages.
+
+See ``README.md`` for the difficulty model and regeneration steps.
+"""
diff --git a/python_pkg/byox_ladder/build_pages.py b/python_pkg/byox_ladder/build_pages.py
new file mode 100755
index 00000000..05a49f93
--- /dev/null
+++ b/python_pkg/byox_ladder/build_pages.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+"""Build the standalone HTML ladder pages from templates and parsed guide data.
+
+Produces two self-contained, dependency-free pages next to this script:
+
+* ``guide-ladder.html`` — every individual guide, built by injecting
+ ``guides.json`` (written by ``parse_guides.py``) into the guide template.
+* ``category-ladder.html`` — the 30-category ladder, a static template.
+
+Both are wrapped in a minimal HTML document skeleton so they open directly via
+``file://`` with no server, fonts, or network access. Run ``make build`` to
+regenerate the whole pipeline.
+"""
+
+from __future__ import annotations
+
+import json
+import logging
+from pathlib import Path
+
+logger = logging.getLogger(__name__)
+
+HERE = Path(__file__).resolve().parent
+TEMPLATES = HERE / "templates"
+GUIDES_JSON = HERE / "guides.json"
+
+# Placeholder in the guide template that the inlined dataset replaces.
+MARKER = "/*__GUIDES__*/"
+
+# Minimal, valid document skeleton added around the published page content
+# (claude.ai injects an equivalent wrapper; local files need their own).
+SKELETON_HEAD = (
+ "\n"
+ '\n'
+ "
\n"
+ '\n'
+ '\n'
+)
+SKELETON_TAIL = "\n\n"
+
+
+def standalone(content: str) -> str:
+ """Wrap page content in a minimal HTML document skeleton."""
+ return SKELETON_HEAD + content + SKELETON_TAIL
+
+
+def build_guide_page() -> str:
+ """Inject ``guides.json`` into the guide template; return standalone HTML."""
+ guides = json.loads(GUIDES_JSON.read_text(encoding="utf-8"))
+ # Compact payload; guard any "" so inline data can't close the script tag.
+ payload = json.dumps(guides, separators=(",", ":")).replace("", "<\\/")
+ template = (TEMPLATES / "guide.template.html").read_text(encoding="utf-8")
+ if MARKER not in template:
+ message = f"marker {MARKER!r} not found in guide template"
+ raise ValueError(message)
+ return standalone(template.replace(MARKER, f"const GUIDES = {payload};"))
+
+
+def build_category_page() -> str:
+ """Return the standalone category ladder (static content, no data)."""
+ content = (TEMPLATES / "category.html").read_text(encoding="utf-8")
+ return standalone(content)
+
+
+def main() -> int:
+ """Write both standalone ladder pages next to this script; return an exit code."""
+ if not GUIDES_JSON.exists():
+ logger.error(
+ "missing %s — run parse_guides.py first (or make build)", GUIDES_JSON
+ )
+ return 1
+ (HERE / "guide-ladder.html").write_text(build_guide_page(), encoding="utf-8")
+ (HERE / "category-ladder.html").write_text(build_category_page(), encoding="utf-8")
+ logger.info("wrote guide-ladder.html and category-ladder.html")
+ return 0
+
+
+if __name__ == "__main__":
+ logging.basicConfig(level=logging.INFO, format="%(message)s")
+ raise SystemExit(main())
diff --git a/python_pkg/byox_ladder/category-ladder.html b/python_pkg/byox_ladder/category-ladder.html
new file mode 100644
index 00000000..72135767
--- /dev/null
+++ b/python_pkg/byox_ladder/category-ladder.html
@@ -0,0 +1,363 @@
+
+
+
+
+
+Build Your Own X — Difficulty Ladder
+
+
+
+
+
codecrafters-io / build-your-own-x
+
The Build-Your-Own-X Difficulty Ladder
+
All 30 project categories from the
+ build-your-own-x
+ collection, ranked easiest → hardest and grouped into four tiers.
+ Sort or filter, then follow a card to its tutorials.
+
+
+
PrerequisitesDomain knowledge a beginner must bring — crypto, linear algebra, OS internals.
+
ScopeHow much code a working toy version actually needs.
+
GuidanceWhether a canonical step-by-step tutorial exists to lean on.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python_pkg/byox_ladder/guide-ladder.html b/python_pkg/byox_ladder/guide-ladder.html
new file mode 100644
index 00000000..c445e4e5
--- /dev/null
+++ b/python_pkg/byox_ladder/guide-ladder.html
@@ -0,0 +1,320 @@
+
+
+
+
+
+Build Your Own X — Guide Ladder
+
+
+
+
+
codecrafters-io / build-your-own-x
+
The Build-Your-Own-X Guide Ladder
+
Every individual tutorial from the
+ build-your-own-x
+ collection — 0 guides across 30 categories — placed on a difficulty
+ ladder. Search, filter by tier / language / category, and click any guide to open it.
+
How difficulty is assigned: each guide inherits its category's tier
+ (a Regex-Engine guide is easier than an OS guide, whatever the language). Language only nudges
+ the order within a tier — lower-level languages (C, Rust, Asm) sit a little higher than
+ Python or JS. The Unsorted group is the repo's own uncategorized bucket; its guides span
+ all levels, so they aren't ranked.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python_pkg/byox_ladder/parse_guides.py b/python_pkg/byox_ladder/parse_guides.py
new file mode 100755
index 00000000..a5d75c7b
--- /dev/null
+++ b/python_pkg/byox_ladder/parse_guides.py
@@ -0,0 +1,197 @@
+#!/usr/bin/env python3
+"""Parse the build-your-own-x README into a ranked, tiered list of guides.
+
+Reads a local copy of the upstream README (``byox-readme.md`` — fetch it with the
+command documented in this folder's ``README.md``, or run ``make build``) and
+writes ``guides.json``.
+
+Difficulty model: every guide inherits its category's tier — the dominant
+signal — while its language only nudges the ordering *within* that tier
+(lower-level languages such as C or Rust sort a little harder than Python or
+JavaScript). See ``README.md`` for the full rationale.
+"""
+
+from __future__ import annotations
+
+import json
+import logging
+from pathlib import Path
+import re
+
+logger = logging.getLogger(__name__)
+
+HERE = Path(__file__).resolve().parent
+README = HERE / "byox-readme.md"
+OUTPUT = HERE / "guides.json"
+
+# Category display name -> (global difficulty rank 1..30, tier). The rank order
+# mirrors the approved per-category ladder; keys match the exact backtick names
+# used in the README headers.
+CATEGORIES: dict[str, tuple[int, str]] = {
+ "Command-Line Tool": (1, "beginner"),
+ "Bot": (2, "beginner"),
+ "Template Engine": (3, "beginner"),
+ "Text Editor": (4, "beginner"),
+ "Git": (5, "beginner"),
+ "Shell": (6, "intermediate"),
+ "Web Server": (7, "intermediate"),
+ "Search Engine": (8, "intermediate"),
+ "BitTorrent Client": (9, "intermediate"),
+ "Front-end Framework / Library": (10, "intermediate"),
+ "Regex Engine": (11, "intermediate"),
+ "Game": (12, "intermediate"),
+ "Programming Language": (13, "advanced"),
+ "Emulator / Virtual Machine": (14, "advanced"),
+ "Neural Network": (15, "advanced"),
+ "Memory Allocator": (16, "advanced"),
+ "Blockchain / Cryptocurrency": (17, "advanced"),
+ "Database": (18, "advanced"),
+ "3D Renderer": (19, "advanced"),
+ "Physics Engine": (20, "advanced"),
+ "Docker": (21, "advanced"),
+ "Voxel Engine": (22, "advanced"),
+ "Network Stack": (23, "expert"),
+ "AI Model": (24, "expert"),
+ "Visual Recognition System": (25, "expert"),
+ "Augmented Reality": (26, "expert"),
+ "Web Browser": (27, "expert"),
+ "Processor": (28, "expert"),
+ "Operating System": (29, "expert"),
+ "Distributed Systems": (30, "expert"),
+}
+
+# Rank used for the repo's "Uncategorized" bucket, sorted after every real tier.
+UNSORTED_RANK = 99
+
+# Per-language ordering nudge (0.0 = high-level, higher = lower-level / more
+# effort). Only orders guides *within* a category; it never crosses a tier.
+LANG_EFFORT: dict[str, float] = {
+ "assembly": 0.9,
+ "asm": 0.9,
+ "c": 0.8,
+ "c++": 0.8,
+ "cpp": 0.8,
+ "rust": 0.75,
+ "zig": 0.75,
+ "d": 0.7,
+ "nim": 0.6,
+ "haskell": 0.6,
+ "ocaml": 0.6,
+ "go": 0.55,
+ "scala": 0.55,
+ "c#": 0.5,
+ "java": 0.5,
+ "kotlin": 0.5,
+ "swift": 0.5,
+ "f#": 0.5,
+ "clojure": 0.45,
+ "elixir": 0.4,
+ "shell": 0.4,
+ "bash": 0.4,
+ "r": 0.4,
+ "typescript": 0.35,
+ "php": 0.35,
+ "perl": 0.35,
+ "dart": 0.35,
+ "javascript": 0.3,
+ "node.js": 0.3,
+ "node": 0.3,
+ "ruby": 0.3,
+ "python": 0.3,
+ "lua": 0.3,
+ "any": 0.25,
+ "pseudocode": 0.2,
+}
+
+# Default nudge for a language not present in ``LANG_EFFORT``.
+DEFAULT_EFFORT = 0.5
+
+HEADER_RE = re.compile(r"^####\s+Build your own\s+`([^`]+)`")
+UNCAT_RE = re.compile(r"^####\s+Uncategorized")
+GUIDE_RE = re.compile(r"^\*\s+\[(.+)\]\((\S+?)\)(.*)$")
+LABEL_RE = re.compile(r"^\*\*(.+?)\*\*:\s*(.*)$")
+
+
+def split_langs(raw: str) -> list[str]:
+ """Split a ``C# / TypeScript`` language blob into a clean list of names."""
+ parts = re.split(r"[/,&]| or ", raw)
+ names = [part.strip().strip("*").strip() for part in parts]
+ cleaned = [name for name in names if name]
+ return cleaned or ["Various"]
+
+
+def lang_effort(langs: list[str]) -> float:
+ """Return the easiest listed language's nudge (you pick your best one)."""
+ values = [LANG_EFFORT.get(lang.lower(), DEFAULT_EFFORT) for lang in langs]
+ return min(values) if values else DEFAULT_EFFORT
+
+
+def clean_title(rest: str) -> str:
+ """Strip surrounding markdown italics/emphasis from a guide title."""
+ title = rest.strip()
+ title = re.sub(r"^_+", "", title)
+ title = re.sub(r"_+$", "", title)
+ return title.strip().strip("*").strip()
+
+
+def parse_guide_line(line: str, category: str) -> dict[str, object] | None:
+ """Parse one ``* [**Lang**: _Title_](url)`` line into a guide record."""
+ match = GUIDE_RE.match(line.strip())
+ if match is None:
+ return None
+ label, url, trailer = match.group(1), match.group(2), match.group(3)
+ label_match = LABEL_RE.match(label)
+ if label_match is not None:
+ langs = split_langs(label_match.group(1))
+ title = clean_title(label_match.group(2))
+ else:
+ langs = ["Various"]
+ title = clean_title(label)
+ rank, tier = CATEGORIES.get(category, (UNSORTED_RANK, "unsorted"))
+ return {
+ "title": title,
+ "url": url,
+ "langs": langs,
+ "category": category,
+ "cat_rank": rank,
+ "tier": tier,
+ "video": "[video]" in trailer.lower(),
+ "score": round(rank * 10 + lang_effort(langs), 2),
+ }
+
+
+def parse_readme(text: str) -> list[dict[str, object]]:
+ """Parse the whole README text into a score-sorted list of guides."""
+ guides: list[dict[str, object]] = []
+ current: str | None = None
+ for line in text.splitlines():
+ header = HEADER_RE.match(line)
+ if header is not None:
+ current = header.group(1).strip()
+ continue
+ if UNCAT_RE.match(line):
+ current = "Uncategorized"
+ continue
+ if current is None:
+ continue
+ guide = parse_guide_line(line, current)
+ if guide is not None:
+ guides.append(guide)
+ guides.sort(key=lambda item: (item["score"], str(item["title"]).lower()))
+ return guides
+
+
+def main() -> int:
+ """Parse ``byox-readme.md`` and write ``guides.json``; return an exit code."""
+ if not README.exists():
+ logger.error("missing %s — fetch it first (see README.md / make build)", README)
+ return 1
+ guides = parse_readme(README.read_text(encoding="utf-8"))
+ OUTPUT.write_text(json.dumps(guides, indent=1), encoding="utf-8")
+ logger.info("wrote %s (%d guides)", OUTPUT.name, len(guides))
+ return 0
+
+
+if __name__ == "__main__":
+ logging.basicConfig(level=logging.INFO, format="%(message)s")
+ raise SystemExit(main())
diff --git a/python_pkg/byox_ladder/templates/category.html b/python_pkg/byox_ladder/templates/category.html
new file mode 100644
index 00000000..cfbfbdc1
--- /dev/null
+++ b/python_pkg/byox_ladder/templates/category.html
@@ -0,0 +1,356 @@
+Build Your Own X — Difficulty Ladder
+
+
+
+
+
codecrafters-io / build-your-own-x
+
The Build-Your-Own-X Difficulty Ladder
+
All 30 project categories from the
+ build-your-own-x
+ collection, ranked easiest → hardest and grouped into four tiers.
+ Sort or filter, then follow a card to its tutorials.
+
+
+
PrerequisitesDomain knowledge a beginner must bring — crypto, linear algebra, OS internals.
+
ScopeHow much code a working toy version actually needs.
+
GuidanceWhether a canonical step-by-step tutorial exists to lean on.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python_pkg/byox_ladder/templates/guide.template.html b/python_pkg/byox_ladder/templates/guide.template.html
new file mode 100644
index 00000000..5f26214e
--- /dev/null
+++ b/python_pkg/byox_ladder/templates/guide.template.html
@@ -0,0 +1,313 @@
+Build Your Own X — Guide Ladder
+
+
+
+
+
codecrafters-io / build-your-own-x
+
The Build-Your-Own-X Guide Ladder
+
Every individual tutorial from the
+ build-your-own-x
+ collection — 0 guides across 30 categories — placed on a difficulty
+ ladder. Search, filter by tier / language / category, and click any guide to open it.
+
How difficulty is assigned: each guide inherits its category's tier
+ (a Regex-Engine guide is easier than an OS guide, whatever the language). Language only nudges
+ the order within a tier — lower-level languages (C, Rust, Asm) sit a little higher than
+ Python or JS. The Unsorted group is the repo's own uncategorized bucket; its guides span
+ all levels, so they aren't ranked.