diff --git a/Makefile b/Makefile
index a3b6dd2..c330cef 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: build validate audit eval site graph track synthesize recap-dry clockbench fair check help
+.PHONY: build validate audit eval site graph track synthesize recap-dry clockbench fair repos check help
help:
@echo "longevity-loop — an AI-native compounding loop for aging science"
@@ -9,7 +9,8 @@ help:
@echo " make synthesize Draft frontier+roadmap proposals into data/_synthesis.md (human-gated)"
@echo " make clockbench Cross-clock disagreement benchmark (measures gaps-analysis G1)"
@echo " make fair Score open aging datasets for FAIR/reproducibility → docs/FAIR.md (G2)"
- @echo " make check validate + audit + eval + clockbench + fair + build drift-gate (CI finish line)"
+ @echo " make repos Per-repo KG + agentic-tooling generation recipes → docs/REPOS.md (hub)"
+ @echo " make check validate + audit + eval + clockbench + fair + repos + build drift-gate (CI finish line)"
build:
python3 scripts/build.py
@@ -54,11 +55,17 @@ clockbench:
fair:
python3 scripts/fair.py
+# Per-repo KG + agentic-tooling generation recipes → docs/REPOS.md (docs/HUB_ARCHITECTURE.md).
+repos:
+ python3 scripts/repos.py
+
# Finish line: data well-formed, still an AI-native loop, executions honestly eval'd
-# (no fake ✅), the clock-disagreement + FAIR metrics self-verify, and generated docs match.
+# (no fake ✅), the clock-disagreement + FAIR + repo-recipe artifacts self-verify, and docs match.
check: validate audit eval
python3 scripts/clockbench.py --selftest
python3 scripts/fair.py --selftest
python3 scripts/fair.py --gate
python3 scripts/fair.py --check
+ python3 scripts/repos.py --selftest
+ python3 scripts/repos.py --check
python3 scripts/build.py --check
diff --git a/README.md b/README.md
index f763b52..88af394 100644
--- a/README.md
+++ b/README.md
@@ -157,7 +157,7 @@ Sequenced bridge: convert the computational finding + your credential network (G
✅ Roadmap Execution
-**11/22 done (50%).** Each execution is a checkbox with a before→after eval; a box only ticks with a real result (`done` requires a non-pending before AND after — no evidence ⇒ not done, gated in CI).
+**12/23 done (52%).** Each execution is a checkbox with a before→after eval; a box only ticks with a real result (`done` requires a non-pending before AND after — no evidence ⇒ not done, gated in CI).
- [x] **E1** (P1 · Foundation) — Stand up the AI-native loop (repo + self-audit + gated `make check`)
- [x] **E2** (P1 · Foundation) — Map the field into a verified knowledge base
@@ -181,6 +181,7 @@ Sequenced bridge: convert the computational finding + your credential network (G
- [x] **E20** (P1 · Foundation) — Learn from mims-harvard/ATHENA; integrate the frontier signal into the field map
- [x] **E21** (P1 · Foundation) — Problem map: top aging problems as a relationship graph + the one target + low-hanging fruit
- [x] **E22** (P1 · Foundation) — Publish the tracker.json data contract for the interactive portfolio instance (data-first slice)
+- [x] **E23** (P1 · Foundation) — Hub architecture (knowledge·tooling·experts per cited repo) + the per-repo generation-recipe layer
### Eval reports — before → after
@@ -208,6 +209,7 @@ Sequenced bridge: convert the computational finding + your credential network (G
| ✅ E20 | external frontier work studied → integrated (cited) into the loop | ATHENA / ToolUniverse / Zitnik not tracked; no study on file | research/athena-study.md (cited analysis) + Zitnik (people), ATHENA-R1 (frontier), ToolUniverse (stack) added; finding: ATHENA's citation-allow-list = our existing no-evidence-no-claim; RAG/multi-agent/ToolUniverse overkill for a solo code-only loop |
| ✅ E21 | a clear, visual answer to 'what are we shooting, and what's the quick win' | gaps ranked (gaps-analysis.md) but relationships + the single target + quick wins were implicit | docs/PROBLEMS.md — Mermaid relationship graph (G1 measurement = root bottleneck we target) + top-problems table + how/when/why + 3 low-hanging fruit with done-when; README pointer |
| ✅ E22 | a stable, derived JSON contract the agentic-portfolio instance can consume (no drift) | no machine-readable mission/progress feed for an external interactive surface | site/tracker.json — mission + 48% progress + 21 milestones + roadmap + low-hanging + frontier + 12 hubs + links, generated by build_site.py, deployed on Pages |
+| ✅ E23 | a fresh, cheap, future-proof way to offer a KG + agentic tooling for each cited repo | cited repos listed in stack.yml but no KG/tooling per repo; no design for how to do it well | docs/HUB_ARCHITECTURE.md (index+JIT design; reuse graphify/understand/reverse; MCP; SHA-cached) + scripts/repos.py → docs/REPOS.md (22 repos, per-repo KG + reverse recipes), gated in make check |
---
@@ -228,6 +230,12 @@ Sequenced bridge: convert the computational finding + your credential network (G
---
+🧩 Knowledge · Tooling · Experts hub
+
+This repo is a **hub**: for each cited repo it offers a knowledge graph (knowledge) + agentic tooling (skills/plugins/workflows), and links them to the experts who build them. Design (high-quality · fast · cheap · fresh · future-proof): **[docs/HUB_ARCHITECTURE.md](docs/HUB_ARCHITECTURE.md)** · per-repo generation recipes: **[docs/REPOS.md](docs/REPOS.md)**. Fresh index, just-in-time depth — deep artifacts are generated on demand and cached by commit SHA, never committed stale.
+
+---
+
🎯 What we're shooting
The top aging problems, how they relate, the **one root bottleneck we target** (G1 — measuring aging), and the **low-hanging fruit** for quick wins — as a graph: **[docs/PROBLEMS.md](docs/PROBLEMS.md)**. Root bottleneck = our lane; everything downstream is gated on being able to measure aging in a shared, validated way.
diff --git a/data/executions.yml b/data/executions.yml
index 17b830c..afabc6a 100644
--- a/data/executions.yml
+++ b/data/executions.yml
@@ -159,3 +159,12 @@
before: "no machine-readable mission/progress feed for an external interactive surface"
after: "site/tracker.json — mission + 48% progress + 21 milestones + roadmap + low-hanging + frontier + 12 hubs + links, generated by build_site.py, deployed on Pages"
proof: "scripts/build_site.py · site/tracker.json · research/portfolio-extension-study.md"
+- id: E23
+ phase: "P1 · Foundation"
+ item: "Hub architecture (knowledge·tooling·experts per cited repo) + the per-repo generation-recipe layer"
+ done: true
+ eval:
+ metric: "a fresh, cheap, future-proof way to offer a KG + agentic tooling for each cited repo"
+ before: "cited repos listed in stack.yml but no KG/tooling per repo; no design for how to do it well"
+ after: "docs/HUB_ARCHITECTURE.md (index+JIT design; reuse graphify/understand/reverse; MCP; SHA-cached) + scripts/repos.py → docs/REPOS.md (22 repos, per-repo KG + reverse recipes), gated in make check"
+ proof: "docs/HUB_ARCHITECTURE.md · scripts/repos.py · docs/REPOS.md · make check"
diff --git a/docs/HUB_ARCHITECTURE.md b/docs/HUB_ARCHITECTURE.md
new file mode 100644
index 0000000..70ba4a4
--- /dev/null
+++ b/docs/HUB_ARCHITECTURE.md
@@ -0,0 +1,106 @@
+# The hub architecture — knowledge · tooling · experts, per cited repo
+
+_How this repo becomes the **hub** that offers, for each top cited repo, a **knowledge graph**
+(knowledge) and **agentic tooling** (skills / plugins / workflows) — engineered to be high-quality,
+fast, cheap, always up-to-date, and future-proof._
+
+---
+
+## The one decision that determines everything: INDEX + JIT, not a warehouse of frozen artifacts
+
+The tempting move — eagerly generate and commit a KG + a skill + a plugin + a workflow for all ~27
+cited repos — is the wrong one, and it fails **every** quality bar at once:
+
+| Bar | Why the "generate-all-now" approach fails it |
+|---|---|
+| High quality | Bulk auto-generation is slop — 27 repos × 4 artifacts, none verified against the real repo. |
+| Fast | Serial LLM generation over 100+ artifacts is slow to produce and slow to review. |
+| Cheap | You pay LLM cost for every repo × every artifact, including repos no one ever uses. |
+| Up-to-date | Repos push weekly; a committed KG/skill is **stale the day after** you generate it. |
+| Future-proof | A frozen pile of bespoke artifacts is a maintenance graveyard, not an asset. |
+
+So the architecture is three layers, each optimized for a different bar:
+
+```
+① INDEX (committed, cheap, always fresh) ── the hub's front door
+ registry (data/stack.yml) + live GitHub metadata → a ranked, queryable repo index
+ │ (cheap: API only, no LLM · fresh: weekly cron · deterministic)
+ ▼
+② RECIPE (committed, deterministic) ── how to make the deep artifacts, per repo
+ per-repo generation commands: KG via graphify/understand-anything · tooling via anyagent reverse
+ │ (this file's PoC: scripts/repos.py → docs/REPOS.md)
+ ▼
+③ JIT DEEP (generated on demand, cached by commit SHA, NOT committed) ── the actual KG + skills
+ run the recipe only when you need a repo, cache it, regenerate only when the repo's SHA changes
+ │ (quality: verified/gated · cheap: never for an unused repo · fresh: SHA-keyed)
+ ▼
+④ INTEROP (MCP + portable formats) ── future-proof
+ KGs as JSON · skills as SKILL.md · tools via `anyagent mcp` → any agent client can consume the hub
+```
+
+**The thesis in one line:** keep a *fresh, cheap index* committed; generate the *expensive deep
+artifacts just-in-time* by reusing existing engines, keyed to the source commit so they're never
+stale — you never pay for a repo you don't use, and you never ship one that's out of date.
+
+---
+
+## Reuse the engines — don't rebuild them (backbone: *call it, don't absorb it*)
+
+Every deep artifact maps to a tool that already exists. The hub **orchestrates**; it does not
+reimplement graph extraction or skill synthesis.
+
+| Deep artifact | Engine to reuse | Input |
+|---|---|---|
+| Knowledge graph (codebase) | `understand-anything` (code → architecture KG) | a cloned repo |
+| Knowledge graph (docs/model card/paper) | `graphify` (any input → clustered KG + HTML/JSON) | README / model card / paper |
+| Agentic tooling blueprint | `anyagent reverse --markdown` | repo URL |
+| Skill / workflow from that blueprint | `anyagent build` / `refine` (gated to a score) | the blueprint |
+| Serve it to any client | `anyagent mcp` (MCP) / a `SKILL.md` | the generated tool |
+| Live repo metadata | GitHub API (already in `scripts/track.py`) | the repo slug |
+
+If a better KG engine appears, you swap the **recipe**, not the data — that's the future-proofing.
+
+---
+
+## How each quality bar is met
+
+- **High quality** — *no evidence ⇒ no claim* applied to generation: each artifact records the source
+ repo + **commit SHA** it was built from, and a skill only ships if `anyagent refine` clears its
+ score gate (does it build? does it run?). Provenance = reproducible + drift-detectable.
+- **Fast** — the index is API-only (seconds); deep generation fans out **per-repo in parallel**
+ (subagents / a workflow), and only for repos you asked for or that changed.
+- **Cheap** — zero LLM in the index. LLM only in ③, only JIT, only when the SHA moved. An unchanged
+ repo is never regenerated; an unused repo is never generated at all.
+- **Up-to-date** — the weekly cron (extend `track.yml`) refreshes ① and stamps each deep artifact with
+ its source SHA; when the repo moves, the artifact is flagged **stale**, not silently wrong.
+- **Future-proof** — portable standard formats (JSON KG, `SKILL.md`, MCP), registry-driven from the
+ single source of truth (`data/stack.yml`), swappable engines. The hub is MCP-queryable, so any
+ future client (Claude Desktop/Code, another agent) consumes it without bespoke glue.
+
+## The third pillar — experts — is already wired
+
+`people.yml` + `frontier.yml` + the bi-temporal field graph (`build_graph.py`) already link
+experts ↔ orgs ↔ topics ↔ recent works. The hub adds the missing edge: **repo → maintainer/lab →
+expert**, so "knowledge (KG) · tooling (skills) · experts (people)" become one graph you can traverse
+(e.g. *pyaging → rsinghlab → the clocks experts to reach*).
+
+---
+
+## Phased rollout (decompose — never one-shot)
+
+- **Phase 0 (this PR):** the RECIPE layer — `scripts/repos.py` turns `data/stack.yml` into a per-repo
+ generation index (`docs/REPOS.md`): KG command + tooling command + provenance, deterministic and
+ gated. Proves the model with **zero** wasteful generation.
+- **Phase 1:** enrich ① with live GitHub metadata (stars, last-push SHA, license, health) on the
+ weekly cron — extend `track.py`; write a generated `data/_repos.yml` (like `_frontier.yml`).
+- **Phase 2:** JIT deep generation for the **top-N** repos only (start with the North-Star lane:
+ Biolearn, pyaging, Geneformer, scGPT), via `graphify` + `anyagent reverse`, cached by SHA, exposed
+ via MCP. Verified/gated before publish.
+- **Phase 3:** surface the hub in the agentic-portfolio instance (reads the same `tracker.json` +
+ `_repos.yml`), so the KG/tooling/experts hub is browsable and agent-answerable.
+
+**Anti-pattern to avoid at every phase:** committing a deep artifact without its source SHA, or
+generating for a repo before someone needs it. Fresh index, lazy depth.
+
+_Not medical advice. All artifacts are computational metadata about open-source repos; no wet-lab or
+therapeutic claim._
diff --git a/docs/REPOS.md b/docs/REPOS.md
new file mode 100644
index 0000000..a434c2c
--- /dev/null
+++ b/docs/REPOS.md
@@ -0,0 +1,51 @@
+# Repo hub — generation recipes (RECIPE layer)
+
+_Generated from `data/stack.yml` + `data/frontier.yml` by `scripts/repos.py`. See the design in [HUB_ARCHITECTURE.md](HUB_ARCHITECTURE.md)._
+
+**22 cited repos** (20 on GitHub). This is the **recipe**, not the artifacts: run a repo's commands **on demand** and cache the result by the repo's commit SHA — deep knowledge graphs and skills are made just-in-time, never committed stale. You never pay for a repo you don't use.
+
+## The recipe (per repo)
+
+```bash
+# 1. knowledge graph
+understand-anything # GitHub codebases (architecture KG)
+graphify # model cards / docs / papers (clustered KG)
+# 2. agentic tooling (skill / plugin / workflow)
+anyagent reverse --markdown # → blueprint, then:
+anyagent build/refine "" --target-score 80 # → a gated skill/workflow
+# 3. serve to any client
+anyagent mcp # expose the generated tools over MCP
+```
+
+> Quality gate: stamp every generated artifact with the source repo + commit SHA (reproducible + staleness-detectable); a skill ships only after `refine` clears its score.
+
+## The repos
+
+| Repo | Kind | Host | KG engine | Tooling |
+|---|---|---|---|---|
+| [BixBench](https://github.com/Future-House/BixBench) | benchmark | github | `understand-anything` | `anyagent reverse Future-House/BixBench` |
+| [LAB-Bench](https://github.com/Future-House/LAB-Bench) | benchmark | github | `understand-anything` | `anyagent reverse Future-House/LAB-Bench` |
+| [Open Problems in Single-Cell](https://github.com/openproblems-bio/openproblems) | benchmark | github | `understand-anything` | `anyagent reverse openproblems-bio/openproblems` |
+| [DunedinPACE](https://github.com/danbelsky/DunedinPACE) | clock | github | `understand-anything` | `anyagent reverse danbelsky/DunedinPACE` |
+| [scAge](https://github.com/alex-trapp/scAge) | clock | github | `understand-anything` | `anyagent reverse alex-trapp/scAge` |
+| [Marinka Zitnik](https://github.com/mims-harvard/ATHENA) | frontier-work | github | `understand-anything` | `anyagent reverse mims-harvard/ATHENA` |
+| [Morgan Levine](https://github.com/HigginsChenLab/methylCIPHER) | frontier-work | github | `understand-anything` | `anyagent reverse HigginsChenLab/methylCIPHER` |
+| [cognee](https://github.com/topoteretes/cognee) | graph | github | `understand-anything` | `anyagent reverse topoteretes/cognee` |
+| [Graphiti](https://github.com/getzep/graphiti) | graph | github | `understand-anything` | `anyagent reverse getzep/graphiti` |
+| [PyTorch Geometric Temporal](https://github.com/benedekrozemberczki/pytorch_geometric_temporal) | graph | github | `understand-anything` | `anyagent reverse benedekrozemberczki/pytorch_geometric_temporal` |
+| [TGB (Temporal Graph Benchmark)](https://github.com/shenyangHuang/TGB) | graph | github | `understand-anything` | `anyagent reverse shenyangHuang/TGB` |
+| [AltumAge](https://github.com/rsinghlab/AltumAge) | model | github | `understand-anything` | `anyagent reverse rsinghlab/AltumAge` |
+| [ESM-2](https://github.com/facebookresearch/esm) | model | github | `understand-anything` | `anyagent reverse facebookresearch/esm` |
+| [Geneformer](https://huggingface.co/ctheodoris/Geneformer) | model | huggingface | `graphify` | `anyagent reverse ` |
+| [scGPT](https://github.com/bowang-lab/scGPT) | model | github | `understand-anything` | `anyagent reverse bowang-lab/scGPT` |
+| [TxGNN](https://github.com/mims-harvard/TxGNN) | model | github | `understand-anything` | `anyagent reverse mims-harvard/TxGNN` |
+| [AlphaFold](https://github.com/google-deepmind/alphafold) | tool | github | `understand-anything` | `anyagent reverse google-deepmind/alphafold` |
+| [Biolearn](https://bio-learn.github.io/) | tool | web | `graphify` | `anyagent reverse ` |
+| [CZ CELLxGENE Census](https://github.com/chanzuckerberg/cellxgene-census) | tool | github | `understand-anything` | `anyagent reverse chanzuckerberg/cellxgene-census` |
+| [pyaging](https://github.com/rsinghlab/pyaging) | tool | github | `understand-anything` | `anyagent reverse rsinghlab/pyaging` |
+| [scanpy](https://github.com/scverse/scanpy) | tool | github | `understand-anything` | `anyagent reverse scverse/scanpy` |
+| [ToolUniverse](https://github.com/mims-harvard/ToolUniverse) | tool | github | `understand-anything` | `anyagent reverse mims-harvard/ToolUniverse` |
+
+---
+
+KG engine split: `understand-anything` for the 20 GitHub codebases, `graphify` for model cards / docs. PR a repo into `data/stack.yml` and it joins the hub automatically.
diff --git a/scripts/build.py b/scripts/build.py
index 68cdd73..7bd95d6 100644
--- a/scripts/build.py
+++ b/scripts/build.py
@@ -143,6 +143,15 @@ def render_readme() -> str:
"carries a datasheet + an honest self-assessment (unknown ⇒ no). Full scorecard with "
"load recipes: **[docs/FAIR.md](docs/FAIR.md)**.", "", "---", ""]
+ # The hub (static pointer to the repo-recipe index + architecture)
+ L += ['🧩 Knowledge · Tooling · Experts hub
', "",
+ "This repo is a **hub**: for each cited repo it offers a knowledge graph (knowledge) + "
+ "agentic tooling (skills/plugins/workflows), and links them to the experts who build them. "
+ "Design (high-quality · fast · cheap · fresh · future-proof): "
+ "**[docs/HUB_ARCHITECTURE.md](docs/HUB_ARCHITECTURE.md)** · per-repo generation recipes: "
+ "**[docs/REPOS.md](docs/REPOS.md)**. Fresh index, just-in-time depth — deep artifacts are "
+ "generated on demand and cached by commit SHA, never committed stale.", "", "---", ""]
+
# What we're shooting (static pointer to the problem map)
L += ['🎯 What we\'re shooting
', "",
"The top aging problems, how they relate, the **one root bottleneck we target** "
diff --git a/scripts/repos.py b/scripts/repos.py
new file mode 100644
index 0000000..9e9ff71
--- /dev/null
+++ b/scripts/repos.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python3
+"""repos — the hub's RECIPE layer (docs/HUB_ARCHITECTURE.md, Phase 0).
+
+Turns the registry (data/stack.yml + repo links in data/frontier.yml) into a per-repo
+**generation recipe**: for each cited repo, the exact command to build its knowledge graph
+and to reverse its agentic tooling — deterministically, from the single source of truth.
+
+It deliberately generates NO knowledge graphs or skills here: those are expensive and go
+stale, so they're made just-in-time (cached by commit SHA), not committed. This layer is
+the cheap, always-fresh index of *how* to make them — see docs/HUB_ARCHITECTURE.md.
+
+ repos.py # write docs/REPOS.md
+ repos.py --check # compare instead of writing (drift gate, like build.py --check)
+ repos.py --selftest # assert classification behaves
+"""
+from __future__ import annotations
+
+import re
+import sys
+import pathlib
+
+import yaml
+
+ROOT = pathlib.Path(__file__).resolve().parent.parent
+DATA = ROOT / "data"
+OUT = ROOT / "docs" / "REPOS.md"
+
+
+def load(name: str):
+ p = DATA / f"{name}.yml"
+ return (yaml.safe_load(p.read_text()) if p.exists() else []) or []
+
+
+def classify(url: str) -> tuple[str, str]:
+ """(host, kg_engine) for a repo URL. github codebases → understand-anything;
+ model cards / docs / papers → graphify."""
+ u = (url or "").lower()
+ if "github.com/" in u:
+ return "github", "understand-anything"
+ if "huggingface.co/" in u:
+ return "huggingface", "graphify"
+ return "web", "graphify"
+
+
+def slug(url: str) -> str:
+ m = re.search(r"github\.com/([^/]+/[^/#?]+)", url or "")
+ return m.group(1).rstrip("/") if m else "-"
+
+
+def collect() -> list[dict]:
+ """Cited repos from the stack (models/tools/clocks/datasets/benchmarks) + frontier
+ works that point at a repo. Deduped by URL; only real, generatable sources."""
+ seen, out = set(), []
+ for x in load("stack"):
+ url = x.get("url", "")
+ if not url or url in seen:
+ continue
+ host, engine = classify(url)
+ if host == "web" and "github" not in url and "huggingface" not in url:
+ continue # a plain website (e.g. a portal) isn't a generatable repo — skip
+ seen.add(url)
+ out.append({"name": x.get("name", ""), "kind": x.get("kind", "tool"),
+ "url": url, "host": host, "engine": engine, "slug": slug(url)})
+ for f in load("frontier"):
+ url = f.get("repo", "")
+ if url and url != "-" and url not in seen and ("github.com" in url or "huggingface.co" in url):
+ seen.add(url)
+ host, engine = classify(url)
+ out.append({"name": f.get("name", ""), "kind": "frontier-work",
+ "url": url, "host": host, "engine": engine, "slug": slug(url)})
+ return out
+
+
+def render() -> str:
+ repos = collect()
+ by_engine = {}
+ for r in repos:
+ by_engine.setdefault(r["engine"], []).append(r)
+ gh = sum(1 for r in repos if r["host"] == "github")
+ L = [
+ "# Repo hub — generation recipes (RECIPE layer)", "",
+ "_Generated from `data/stack.yml` + `data/frontier.yml` by `scripts/repos.py`. See the "
+ "design in [HUB_ARCHITECTURE.md](HUB_ARCHITECTURE.md)._", "",
+ f"**{len(repos)} cited repos** ({gh} on GitHub). This is the **recipe**, not the artifacts: "
+ "run a repo's commands **on demand** and cache the result by the repo's commit SHA — deep "
+ "knowledge graphs and skills are made just-in-time, never committed stale. You never pay for "
+ "a repo you don't use.", "",
+ "## The recipe (per repo)", "",
+ "```bash",
+ "# 1. knowledge graph",
+ "understand-anything # GitHub codebases (architecture KG)",
+ "graphify # model cards / docs / papers (clustered KG)",
+ "# 2. agentic tooling (skill / plugin / workflow)",
+ "anyagent reverse --markdown # → blueprint, then:",
+ "anyagent build/refine \"\" --target-score 80 # → a gated skill/workflow",
+ "# 3. serve to any client",
+ "anyagent mcp # expose the generated tools over MCP",
+ "```", "",
+ "> Quality gate: stamp every generated artifact with the source repo + commit SHA "
+ "(reproducible + staleness-detectable); a skill ships only after `refine` clears its score.", "",
+ "## The repos", "",
+ "| Repo | Kind | Host | KG engine | Tooling |",
+ "|---|---|---|---|---|",
+ ]
+ for r in sorted(repos, key=lambda r: (r["kind"], r["name"].lower())):
+ tooling = f"`anyagent reverse {r['slug']}`" if r["host"] == "github" else "`anyagent reverse `"
+ L.append(f"| [{r['name']}]({r['url']}) | {r['kind']} | {r['host']} | `{r['engine']}` | {tooling} |")
+ L += ["", "---", "",
+ f"KG engine split: `understand-anything` for the {gh} GitHub codebases, `graphify` for "
+ "model cards / docs. PR a repo into `data/stack.yml` and it joins the hub automatically.", ""]
+ return "\n".join(L)
+
+
+def selftest() -> int:
+ assert classify("https://github.com/bowang-lab/scGPT")[0] == "github"
+ assert classify("https://github.com/bowang-lab/scGPT")[1] == "understand-anything"
+ assert classify("https://huggingface.co/ctheodoris/Geneformer")[1] == "graphify"
+ assert slug("https://github.com/rsinghlab/pyaging") == "rsinghlab/pyaging"
+ assert collect(), "expected ≥1 cited repo from stack.yml"
+ print(f"repos selftest: OK (classification + slug; {len(collect())} repos collected).")
+ return 0
+
+
+def main() -> int:
+ if "--selftest" in sys.argv:
+ return selftest()
+ text = render()
+ if "--check" in sys.argv:
+ current = OUT.read_text() if OUT.exists() else ""
+ if current != text:
+ print("Out of date: docs/REPOS.md. Run `make repos`.", file=sys.stderr)
+ return 1
+ print("docs/REPOS.md is up to date.")
+ return 0
+ OUT.parent.mkdir(parents=True, exist_ok=True)
+ OUT.write_text(text)
+ print(f"Wrote {OUT.relative_to(ROOT)} — {len(collect())} repos.")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())