Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.0
0.4.0
82 changes: 74 additions & 8 deletions generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
SITE_URL = "https://llm-coding.github.io/"
ORG_SITE_REPO = "llm-coding.github.io"
RESERVED_KEYS = {"icons", "sl", "index", "llms", "style"}
LAYOUT_KEYS = ("order", "extras") # top-level fields in overrides.yaml that are not repositories
KEY_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
ICON_TYPES = {"image/png": "png", "image/svg+xml": "svg", "image/x-icon": "ico",
"image/vnd.microsoft.icon": "ico", "image/jpeg": "jpg", "image/webp": "webp"}
Expand Down Expand Up @@ -83,13 +84,66 @@ def load_overrides(path):
data = yaml.safe_load(path.read_text(encoding="utf-8")) if path.exists() else None
result = {}
for key, v in (data or {}).items():
if key in LAYOUT_KEYS:
continue
v = v or {}
entry = {f: str(v[f]) for f in ("name", "description") if v.get(f)}
entry.update({f: True for f in ("include", "exclude") if v.get(f) is True})
result[str(key)] = entry
return result


def load_layout(path):
"""Return (order, extras) from overrides.yaml: display order and cards without a repository."""
data = (yaml.safe_load(path.read_text(encoding="utf-8")) if path.exists() else None) or {}
if not isinstance(data.get("order") or [], list):
raise ValueError("order must be a list, e.g. order: [a, b]")
extras = data.get("extras") or {}
if not isinstance(extras, dict) or not all(isinstance(v, dict) for v in extras.values()):
raise ValueError("extras must map each key to name, url and description")
order = [str(k) for k in data.get("order") or []]
extras = {str(k): {f: str(v[f]) for f in ("name", "url", "description") if (v or {}).get(f)}
for k, v in extras.items()}
return order, extras


def extra_projects(extras):
return [{"name": k, "title": e.get("name") or k, "description": e.get("description", ""),
"url": e.get("url"), "repo_url": None, "icon": None} for k, e in extras.items()]


def order_projects(projects, order, known_names):
"""Listed names first, in the given order; the rest alphabetically.
Unknown or duplicate names are an error; known but unlisted ones (e.g. archived) are skipped."""
known = {n.lower() for n in known_names}
rank = {}
for i, name in enumerate(order):
if name.lower() not in known:
raise ValueError(f"Unknown name {name!r} in order")
if name.lower() in rank:
raise ValueError(f"Duplicate name {name!r} in order")
rank[name.lower()] = i
return sorted(projects, key=lambda p: (rank.get(p["name"].lower(), len(order)),
p["name"].lower()))


def validate_extras(extras, repo_names, links):
names = {n.lower() for n in repo_names}
for key, e in extras.items():
if not KEY_RE.match(key):
raise ValueError(f"Invalid extras key {key!r}: use a-z, 0-9 and '-'")
if key in RESERVED_KEYS:
raise ValueError(f"Extras key {key!r} is reserved")
if key in names:
raise ValueError(f"Extras key {key!r} collides with a repository")
if key in links:
raise ValueError(f"Extras key {key!r} collides with a shortlink")
if not is_http(e.get("url") or ""):
raise ValueError(f"Extra {key!r} needs an http(s) URL, got {e.get('url')!r}")
if not e.get("description"):
raise ValueError(f"Extra {key!r} needs a description")


def load_yaml(path):
data = yaml.safe_load(path.read_text(encoding="utf-8")) if path.exists() else None
return {str(k): str(v) for k, v in (data or {}).items()}
Expand Down Expand Up @@ -165,12 +219,17 @@ def fetch_icon(site_url):
return None


def drop_shared_icons(icons):
"""Drop icons used by several sites (e.g. the docToolchain default) — they tell nothing apart."""
def drop_shared_icons(icons, extras=()):
"""Drop icons used by several sites (e.g. the docToolchain default) — they tell nothing apart.
An extra that shares an icon loses it, but never takes it away from a repository."""
digests = {}
for name, (_, data, *_) in icons.items():
digests.setdefault(hashlib.sha256(data).hexdigest(), []).append(name)
shared = {n for names in digests.values() if len(names) > 1 for n in names}
shared = set()
for names in digests.values():
repos = [n for n in names if n not in extras]
shared |= set(names) - set(repos) if len(names) > 1 else set()
shared |= set(repos) if len(repos) > 1 else set()
return {n: v for n, v in icons.items() if n not in shared}


Expand Down Expand Up @@ -217,11 +276,12 @@ def render_index(projects, links, version, generated):
for p in projects:
icon = (f'<img class="icon" src="{esc(p["icon"])}" alt="" width="48" height="48">'
if p.get("icon") else initial_icon(p["name"]))
repo = f' · <a href="{esc(p["repo_url"])}">Repository</a>' if p.get("repo_url") else ""
items.append(f"""<li class="card">
{icon}
<div><h2><a href="{esc(p["url"])}">{esc(p.get("title") or p["name"])}</a></h2>
<p>{esc(p["description"])}</p>
<p class="meta"><a href="{esc(p["url"])}">{esc(p["url"])}</a> · <a href="{esc(p["repo_url"])}">Repository</a></p></div>
<p class="meta"><a href="{esc(p["url"])}">{esc(p["url"])}</a>{repo}</p></div>
</li>""")
body = f"""<h1>LLM-Coding projects</h1>
<p class="lead">Open projects about software development with large language models.
Expand Down Expand Up @@ -317,9 +377,12 @@ def main():
all_repos = gh_api_list(f"orgs/{args.org}/repos?per_page=100")
overrides = load_overrides(ROOT / "overrides.yaml")
repos = select_projects(all_repos, overrides)
order, extras = load_layout(ROOT / "overrides.yaml")
links = load_yaml(ROOT / "links.yaml")
# Archived repos keep serving their Pages, so check against every repo name
validate_links(links, [r["name"] for r in all_repos])
repo_names = [r["name"] for r in all_repos]
validate_links(links, repo_names)
validate_extras(extras, repo_names, links)

projects, icons = [], {}
for r in repos:
Expand All @@ -328,10 +391,13 @@ def main():
projects.append({"name": r["name"], "title": display_name(r, overrides),
"description": description(r, overrides),
"url": url, "repo_url": r["html_url"], "icon": None})
icon = fetch_icon(url)
projects += extra_projects(extras)
projects = order_projects(projects, order, repo_names + list(extras))
for p in projects:
icon = fetch_icon(p["url"])
if icon:
icons[r["name"]] = icon
icons = drop_shared_icons(icons)
icons[p["name"]] = icon
icons = drop_shared_icons(icons, set(extras))

version = (ROOT / "VERSION").read_text().strip()
generated = datetime.now(timezone.utc).strftime("%Y-%m-%d")
Expand Down
5 changes: 5 additions & 0 deletions links.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
# Keys: lowercase letters, digits and hyphens. They must not collide with a repository name.
f: https://llm-coding.github.io/fahrrad-reparatur-architektur/
sa: https://llm-coding.github.io/Semantic-Anchors/
rr: https://llm-coding.github.io/vibe-coding-risk-radar/
hcw: https://llm-coding.github.io/Semantic-Anchors/harness-coverage-wheel.html
utc: https://ultrathink.club/
mv: https://llm-coding.github.io/mistral-vibe/
dtc: https://doctoolchain.org/
19 changes: 19 additions & 0 deletions overrides.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@
# description: Short description
# include: true # list this repo although it is a fork
# exclude: true # never list this repo
#
# Two top-level fields are not repositories:
# order: display order on the page and in llms.txt. Repos and extras not listed follow
# alphabetically. Unknown names fail the build.
# extras: cards without a repository. Keys use a-z, 0-9 and '-' and must not collide with
# a repository name or a shortlink. url (http/https) and description are required.
# Because of these fields, repositories named order or extras cannot be overridden.

order: [Semantic-Anchors, vibe-coding-risk-radar, harness-wheel,
ultrathink.club, fahrrad-reparatur-architektur, mistral-vibe, doctoolchain]
extras:
harness-wheel:
name: Harness Coverage Wheel
url: https://llm-coding.github.io/Semantic-Anchors/harness-coverage-wheel.html
description: Check which parts of your agent harness are covered – an interactive wheel, part of Semantic Anchors.
doctoolchain:
name: docToolchain
url: https://doctoolchain.org/
description: "Docs-as-Code toolchain: AsciiDoc, arc42 and diagrams, built and published automatically."

mistral-vibe:
name: Mistral Vibe Doku
Expand Down
135 changes: 135 additions & 0 deletions tests/test_order_extras.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
"""Tests for the display order and the extra (non-repository) cards from overrides.yaml."""
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import generate as g # noqa: E402

LAYOUT = """\
order: [b, wheel, a]
extras:
wheel:
name: Harness Wheel
url: https://x.org/wheel.html
description: "An interactive wheel: try it."
a:
name: A
"""


def card(name, repo_url="https://github.com/LLM-Coding/x"):
return {"name": name, "title": name, "description": "D",
"url": f"https://x.org/{name}/", "repo_url": repo_url, "icon": None}


def test_load_overrides_ignores_order_and_extras(tmp_path):
f = tmp_path / "o.yaml"
f.write_text(LAYOUT)
assert g.load_overrides(f) == {"a": {"name": "A"}}


def test_load_layout_reads_order_and_extras(tmp_path):
f = tmp_path / "o.yaml"
f.write_text(LAYOUT)
order, extras = g.load_layout(f)
assert order == ["b", "wheel", "a"]
assert extras == {"wheel": {"name": "Harness Wheel", "url": "https://x.org/wheel.html",
"description": "An interactive wheel: try it."}}


def test_load_layout_without_fields(tmp_path):
f = tmp_path / "o.yaml"
f.write_text("a:\n name: A\n")
assert g.load_layout(f) == ([], {})
assert g.load_layout(tmp_path / "missing.yaml") == ([], {})


def test_extra_cards_become_projects_without_repository():
extras = {"wheel": {"name": "Harness Wheel", "url": "https://x.org/w.html",
"description": "Wheel"}}
assert g.extra_projects(extras) == [{"name": "wheel", "title": "Harness Wheel",
"description": "Wheel", "url": "https://x.org/w.html",
"repo_url": None, "icon": None}]


def test_order_puts_listed_first_then_rest_alphabetically():
cards = [card("a"), card("B-new"), card("b"), card("c"), card("wheel", None)]
ordered = g.order_projects(cards, ["b", "wheel", "a"], ["a", "b", "B-new", "c", "wheel"])
assert [p["name"] for p in ordered] == ["b", "wheel", "a", "B-new", "c"]


def test_order_matches_repository_names_case_insensitively():
ordered = g.order_projects([card("a"), card("Semantic-Anchors")],
["semantic-anchors"], ["a", "Semantic-Anchors"])
assert [p["name"] for p in ordered] == ["Semantic-Anchors", "a"]


def test_order_skips_known_but_unlisted_repos():
"""An archived repo in the order list must not break the daily build."""
ordered = g.order_projects([card("a")], ["old", "a"], ["a", "old"])
assert [p["name"] for p in ordered] == ["a"]


@pytest.mark.parametrize("order", [["nope"], ["a", "a"]])
def test_order_rejects_unknown_or_duplicate_keys(order):
with pytest.raises(ValueError, match="order"):
g.order_projects([card("a")], order, ["a"])


@pytest.mark.parametrize("extras,err", [
({"Bad Key": {"name": "X", "url": "https://t/"}}, "key"),
({"a": {"name": "X", "url": "https://t/"}}, "repository"),
({"A": {"name": "X", "url": "https://t/"}}, "key"),
({"f": {"name": "X", "url": "https://t/"}}, "shortlink"),
({"icons": {"name": "X", "url": "https://t/"}}, "reserved"),
({"w": {"name": "X", "url": "javascript:alert(1)", "description": "D"}}, "URL"),
({"w": {"name": "X"}}, "URL"),
])
def test_validate_extras_rejects_bad_entries(extras, err):
with pytest.raises(ValueError, match=err):
g.validate_extras(extras, ["a"], {"f": "https://t/"})


def test_validate_extras_rejects_repo_name_in_other_case():
with pytest.raises(ValueError, match="repository"):
g.validate_extras({"semantic-anchors": {"name": "X", "url": "https://t/"}},
["Semantic-Anchors"], {})


def test_extra_card_has_no_repository_link_and_no_label():
html = g.render_index([card("wheel", None)], {}, "1", "d")
assert "Repository" not in html and "None" not in html
assert 'href="https://x.org/wheel/"' in html
assert "external" not in html.lower() and "prototype" not in html.lower()


def test_repo_card_keeps_repository_link():
assert ">Repository</a>" in g.render_index([card("a")], {}, "1", "d")


def test_extra_sharing_a_repo_icon_loses_it_but_the_repo_keeps_it():
"""Review: an extra inside a repo's site must not take that repo's icon away."""
icons = {"repo": ("png", b"same", "u"), "extra": ("png", b"same", "u"),
"r1": ("png", b"dup", "u"), "r2": ("png", b"dup", "u"),
"own": ("png", b"own", "u")}
assert g.drop_shared_icons(icons, extras={"extra", "own"}) == {
"repo": ("png", b"same", "u"), "own": ("png", b"own", "u")}


@pytest.mark.parametrize("text", [
"order: harness-wheel\n",
"extras: [a, b]\n",
"extras:\n w: https://x.org/\n",
])
def test_load_layout_rejects_malformed_fields(tmp_path, text):
f = tmp_path / "o.yaml"
f.write_text(text)
with pytest.raises(ValueError, match="order|extras"):
g.load_layout(f)


def test_validate_extras_requires_description():
with pytest.raises(ValueError, match="description"):
g.validate_extras({"w": {"name": "X", "url": "https://t/"}}, [], {})
Loading