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
17 changes: 11 additions & 6 deletions .github/workflows/daily-update.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: daily-update

# Each run also stores its result under rankings/<date>/<slot>/ (history) and latest/.
# Runs once a day at America/Los_Angeles 00:00 (follows DST).
# Targets America/Los_Angeles 00:00 (follows DST); delayed runs catch up that local day.
# Los Angeles midnight: 07:00 UTC in PDT, 08:00 UTC in PST -> '0 7 * * *' and '0 8 * * *'
# GitHub cron is UTC-only, so both entries fire and tools/schedule_guard.py lets only the
# one that is actually 00:xx in America/Los_Angeles continue; the other one skips.
# Scheduled workflows run from the default branch only. Runs can start a few minutes late.
# GitHub cron is UTC-only, so both entries fire. The guard checks the committed data's
# Los Angeles date rather than requiring execution during 00:xx: late jobs still refresh,
# and the second trigger skips once that local day's data has been committed.
# Scheduled workflows run from the default branch only and may start hours late.
# If Actions is unavailable for the account, use tools/local_daily.sh (see docs/DAILY_UPDATE.md).
on:
schedule:
Expand Down Expand Up @@ -33,13 +34,17 @@ jobs:
env:
EVENT: ${{ github.event_name }}
run: |
# Scheduled runs proceed only at Los Angeles midnight (history folder pt-0000).
# Scheduled runs catch up if this Los Angeles day has no committed refresh (pt-0000).
# Manual dispatch always proceeds: auto -> pt if the clock is at LA midnight, else manual-HHMM.
if [ "$EVENT" = "schedule" ]; then
if python3 tools/schedule_guard.py --tz America/Los_Angeles --hour 0; then
if python3 tools/schedule_guard.py --tz America/Los_Angeles --daily-state data/stats.json; then
echo "run=true" >> "$GITHUB_OUTPUT"
echo "slot=pt" >> "$GITHUB_OUTPUT"
else
status=$?
if [ "$status" -ne 1 ]; then
exit "$status"
fi
echo "run=false" >> "$GITHUB_OUTPUT"
fi
else
Expand Down
33 changes: 18 additions & 15 deletions CURRENT_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,41 @@

## Plan ID

PLAN-005
PLAN-006

## Goal

Production ranking updates run once a day at America/Los_Angeles midnight; public README copy only says it updates daily, with no cron clocks or dual Beijing+PT schedule.
Recover the stale ranking data and let delayed GitHub schedule runs catch up once per Los Angeles calendar day without duplicate refreshes, with consistent daily-update copy on both public landing pages.

## In Scope

- `.github/workflows/daily-update.yml`, `tools/schedule_guard.py` wiring, `docs/launchd/`, `tools/local_daily.sh`, `tools/mac_i18n_cron.sh`, `tools/com.jammyfu.risers-i18n.plist`
- Public copy in `tools/i18n.py` (stamp / a3 / schedule / tagline), `docs/FAQ.md`, `docs/FACTS.md`; operator details in `docs/DAILY_UPDATE.md`
- Tests in `tests/test_schedule.py`, `tests/test_spam.py`, `tests/test_repo.py`; governance files
- `tools/schedule_guard.py`, `.github/workflows/daily-update.yml`, `tests/test_schedule.py`
- `docs/DAILY_UPDATE.md`, English/Simplified Chinese Pages copy, and project governance records
- Owner-authorized merge of PR #19 and deployment verification
- Manual execution and verification of the existing production data workflow

## Out of Scope

- Ranking logic, star-data fetch, translation pipeline, deleting historical `cn-0000` snapshots, merging to `main`
- Ranking calculations, generated data edits, translations, cron frequency, local launchd settings

## Tasks

- [x] Drop the Beijing cron from `daily-update.yml`; keep the two LA UTC crons with `schedule_guard.py`, slot `pt`
- [x] Align local fallback (launchd / cron docs / i18n Mac job) to once daily after US midnight
- [x] Change README / HOW_IT_WORKS strings so readers only see daily update / 每天更新, not clocks
- [x] Update FAQ, FACTS, operator docs and tests that pinned twice-daily / Beijing 00:00
- [x] Re-render generated pages; `python3 tools/verify.py` and `python3 tools/next_plan.py --check` pass
- [x] Open a PR; do not merge
- [x] Diagnose skipped schedule runs and start the existing manual data update
- [x] Add regression tests for late runs, same-day deduplication, DST, invalid state and workflow error propagation
- [x] Gate scheduled refreshes on the committed data's Los Angeles date, preserving manual dispatch and local-hour mode
- [x] Run full verification and document operator behavior and follow-up
- [x] Verify production refresh run 42373937914 and committed data at `2656f40`
- [x] Correct both Pages descriptions and metadata; verify the final PR head
- [ ] Verify the first unattended schedule run after the authorized merge; confirm no same-day duplicate

## Verification

- `python3 tools/verify.py`
- `python3 tools/verify.py --daily`
- `python3 tools/next_plan.py --check`

## Next Candidates

- After merge, confirm the next scheduled `daily-update` run writes `rankings/<date>/pt-0000/`
- Thin Developer Tools / Design / Data new-repo boards: see `TODO_BACKLOG.md`
- Acceleration column (this week vs the week before)
- After owner-approved merge, verify a delayed scheduled run publishes data and the second trigger skips
- Monitor Pages freshness separately from the data workflow if the bot commit does not trigger deployment
- Thin newcomer boards and acceleration column remain in `TODO_BACKLOG.md`
2 changes: 1 addition & 1 deletion TODO_BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Now

- [ ] After the once-daily US schedule PR (PLAN-005) is merged, confirm the next scheduled `daily-update` run writes `rankings/<date>/pt-0000/` (`gh run list --workflow daily-update.yml`)
- [ ] After the authorized PLAN-006 merge, confirm a late `daily-update` writes `rankings/<date>/pt-0000/` and the next same-day trigger skips (`gh run list --workflow daily-update.yml`)
- [ ] Developer Tools new-repo board: 15 of 20 entries were flagged ⚠ in the last run because star-farmed repos use `cli` / `developer-tools`; decide whether to add a quality signal (owner call)
- [ ] Design and Data new-repo boards are thin (0-2 entries): widen with specific topics only (no generic ones; `tests/test_spam.py` guards that)

Expand Down
27 changes: 19 additions & 8 deletions docs/DAILY_UPDATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,25 @@ the Antigravity CLI (`tools/polish_local.sh`, or `tools/local_daily.sh`), never
| America/Los_Angeles 00:00, PDT (UTC-7) | 07:00 UTC | `0 7 * * *` |
| America/Los_Angeles 00:00, PST (UTC-8) | 08:00 UTC | `0 8 * * *` |

GitHub cron only knows UTC, so both LA entries fire every day. `tools/schedule_guard.py --tz America/Los_Angeles --hour 0`
checks the real Los Angeles clock and lets only the one that is actually 00:xx there continue; the other run skips
(you will see a green run whose steps are skipped). Tests cover PDT, PST, both 2026 DST changes (Mar 8, Nov 1) and
assert that exactly one of the two LA slots passes on every day of the year (`tests/test_schedule.py`).
Manual `workflow_dispatch` always runs.

Caveat: GitHub may start scheduled runs several minutes late. A run delayed past the end of the 00:xx hour in Los Angeles
would skip until the next day's slot. Use `workflow_dispatch` (or `tools/local_daily.sh`) if a day needs a manual refresh.
GitHub cron only knows UTC, so both LA entries fire every day. Actions uses
`tools/schedule_guard.py --tz America/Los_Angeles --daily-state data/stats.json` to compare
`generated_at` in the committed data with the current **Los Angeles calendar date**. If today's
refresh is missing, it runs even hours after midnight. Once a refresh for that date is committed,
the second trigger skips. A manual refresh on the same LA day also counts; manual
`workflow_dispatch` itself always runs when requested.

The `daily-update` concurrency group serializes runs, and each job checks out current `main`
before evaluating the gate. A failed fetch/verify/push does not advance the committed timestamp,
so the next trigger can retry. Missing state permits an initial refresh; corrupt, timezone-less or
future timestamps fail the job instead of appearing as a successful skip. Regression tests execute
the real gate shell with stale/current/invalid state and cover PST, PDT, and both DST transitions.
Local `--slot la` retains its hour-based guard; this change is for delayed GitHub Actions runs.

The cron times are targets, not guaranteed start times. On 2026-11-06 both jobs started roughly
seven hours late and the old hour-only guard skipped all data steps. Delayed runs now use their
actual execution day's `pt-0000` archive slot and record the real `generated_at`; no historical
counts or missed-day snapshots are invented. If GitHub drops both triggers entirely, use
`workflow_dispatch` (or `tools/local_daily.sh`) for a manual refresh.

## Option A: GitHub Actions (preferred when it runs)

Expand Down
14 changes: 7 additions & 7 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>GitHub Risers: GitHub Trending Repos Ranked by Star Growth</title>
<meta name="description" content="GitHub trending repositories ranked by stars gained per day, not total stars. AI first, plus developer tools, security, web, infrastructure, design and data. Updated twice a day from the GitHub API.">
<meta name="description" content="GitHub trending repositories ranked by stars gained per day, not total stars. AI first, plus developer tools, security, web, infrastructure, design and data. Updated daily from the GitHub API.">
<link rel="canonical" href="https://jammyfu.github.io/github-risers/">
<link rel="alternate" hreflang="en" href="https://jammyfu.github.io/github-risers/">
<link rel="alternate" hreflang="zh-Hans" href="https://jammyfu.github.io/github-risers/zh-Hans/">
<link rel="alternate" hreflang="x-default" href="https://jammyfu.github.io/github-risers/">
<meta property="og:type" content="website">
<meta property="og:title" content="GitHub Risers: GitHub Trending Repos Ranked by Star Growth">
<meta property="og:description" content="GitHub trending repositories ranked by stars gained per day, not total stars. AI first, plus developer tools, security, web, infrastructure, design and data. Updated twice a day from the GitHub API.">
<meta property="og:description" content="GitHub trending repositories ranked by stars gained per day, not total stars. AI first, plus developer tools, security, web, infrastructure, design and data. Updated daily from the GitHub API.">
<meta property="og:url" content="https://jammyfu.github.io/github-risers/">
<meta property="og:image" content="https://raw.githubusercontent.com/jammyfu/github-risers/main/.github/social-preview.png">
<meta name="twitter:card" content="summary_large_image">
Expand All @@ -27,7 +27,7 @@
"@context": "https://schema.org",
"@type": "Dataset",
"name": "GitHub Risers: GitHub repositories ranked by star growth",
"description": "GitHub trending repositories ranked by stars gained per day, not total stars. AI first, plus developer tools, security, web, infrastructure, design and data. Updated twice a day from the GitHub API.",
"description": "GitHub trending repositories ranked by stars gained per day, not total stars. AI first, plus developer tools, security, web, infrastructure, design and data. Updated daily from the GitHub API.",
"url": "https://jammyfu.github.io/github-risers/",
"sameAs": "https://github.com/jammyfu/github-risers",
"license": "https://opensource.org/license/mit",
Expand Down Expand Up @@ -116,7 +116,7 @@
"name": "How often is it updated?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Twice a day, at 00:00 Beijing time (UTC+8) and 00:00 Los Angeles time. The schedule runs on GitHub Actions, so a run can start a few minutes late. Every run is archived under its own folder in the repository."
"text": "Updated daily. Every run is archived under its own folder in the repository."
}
},
{
Expand Down Expand Up @@ -166,22 +166,22 @@
<body>
<header><div class="w">
<h1>GitHub Risers</h1>
<p>GitHub trending repositories ranked by <strong>star growth</strong> (stars gained per day), not total stars. AI is the main board; developer tools, security, web, infrastructure, design and data have their own. Numbers come straight from the GitHub API and are refreshed twice a day.</p>
<p>GitHub trending repositories ranked by <strong>star growth</strong> (stars gained per day), not total stars. AI is the main board; developer tools, security, web, infrastructure, design and data have their own. Numbers come straight from the GitHub API and are refreshed daily.</p>
<p>It is a script-generated leaderboard: not an official GitHub product, not a manually curated list, and not a star-buying or promotion service.</p>
<a class="btn" href="https://github.com/jammyfu/github-risers#readme">Open the latest rankings</a>
<p class="sw"><a href="https://jammyfu.github.io/github-risers/zh-Hans/" hreflang="zh-Hans">简体中文</a></p>
</div></header>
<main><div class="w">
<h2>Key facts</h2>
<table>
<tr><th>What</th><td>Repositories ranked by stars gained per day over 7, 30 and 365 days, plus a 7-day hottest-new-repos board</td></tr><tr><th>Categories</th><td>AI (main), Developer Tools, Security, Web / Frontend, Infrastructure, Design / Creative, Data</td></tr><tr><th>Updates</th><td>00:00 Beijing time and 00:00 Los Angeles time, every day</td></tr><tr><th>Data source</th><td>GitHub REST API only; missing data is shown as missing, never estimated</td></tr><tr><th>Telemetry</th><td>None</td></tr><tr><th>License</th><td>MIT</td></tr>
<tr><th>What</th><td>Repositories ranked by stars gained per day over 7, 30 and 365 days, plus a 7-day hottest-new-repos board</td></tr><tr><th>Categories</th><td>AI (main), Developer Tools, Security, Web / Frontend, Infrastructure, Design / Creative, Data</td></tr><tr><th>Updates</th><td>Daily</td></tr><tr><th>Data source</th><td>GitHub REST API only; missing data is shown as missing, never estimated</td></tr><tr><th>Telemetry</th><td>None</td></tr><tr><th>License</th><td>MIT</td></tr>
</table>
<h2>Rankings in five languages</h2>
<ul class="l">
<li><a href="https://github.com/jammyfu/github-risers#readme">English</a></li><li><a href="https://github.com/jammyfu/github-risers/blob/main/README.zh-Hans.md">简体中文</a></li><li><a href="https://github.com/jammyfu/github-risers/blob/main/README.zh-Hant.md">繁體中文</a></li><li><a href="https://github.com/jammyfu/github-risers/blob/main/README.ja.md">日本語</a></li><li><a href="https://github.com/jammyfu/github-risers/blob/main/README.ko.md">한국어</a></li>
</ul>
<h2>FAQ</h2>
<details><summary>What is GitHub Risers?</summary><p>A leaderboard of GitHub repositories ranked by how fast they gain stars, not by how many they already have. AI is the main board, with separate boards for developer tools, security, web, infrastructure, design and data.</p></details><details><summary>How is the growth rate computed?</summary><p>per_day = stars gained over the window / days used. The gain is the sum of daily star counts from the GitHub API endpoint stargazers/history over the complete UTC days before the run. A repository younger than the window is divided by its own age and marked young.</p></details><details><summary>How often is it updated?</summary><p>Twice a day, at 00:00 Beijing time (UTC+8) and 00:00 Los Angeles time. The schedule runs on GitHub Actions, so a run can start a few minutes late. Every run is archived under its own folder in the repository.</p></details><details><summary>Is it the same as GitHub Trending?</summary><p>No. GitHub Trending is GitHub&#x27;s own page. GitHub Risers is an independent open-source project with published rules and a saved history of every update. It is not affiliated with GitHub.</p></details><details><summary>What is the hottest new repos board?</summary><p>Repositories created in the last 7 days with at least 100 stars, not forks, not archived, ranked by stars divided by age in days.</p></details><details><summary>Does it detect fake stars?</summary><p>No. It reports the counts the GitHub API returns. It only flags, with a published heuristic, new repos that look like cracked or pirated bundles and ranks them after all normal repos.</p></details><details><summary>Does it use generative AI?</summary><p>The rankings are computed by plain scripts. Translations of repo descriptions can be produced with an AI model in an optional step; numbers never are.</p></details><details><summary>How do I cite it?</summary><p>Use the CITATION.cff file in the repository (GitHub shows a Cite this repository button) and name the snapshot date.</p></details>
<details><summary>What is GitHub Risers?</summary><p>A leaderboard of GitHub repositories ranked by how fast they gain stars, not by how many they already have. AI is the main board, with separate boards for developer tools, security, web, infrastructure, design and data.</p></details><details><summary>How is the growth rate computed?</summary><p>per_day = stars gained over the window / days used. The gain is the sum of daily star counts from the GitHub API endpoint stargazers/history over the complete UTC days before the run. A repository younger than the window is divided by its own age and marked young.</p></details><details><summary>How often is it updated?</summary><p>Updated daily. Every run is archived under its own folder in the repository.</p></details><details><summary>Is it the same as GitHub Trending?</summary><p>No. GitHub Trending is GitHub&#x27;s own page. GitHub Risers is an independent open-source project with published rules and a saved history of every update. It is not affiliated with GitHub.</p></details><details><summary>What is the hottest new repos board?</summary><p>Repositories created in the last 7 days with at least 100 stars, not forks, not archived, ranked by stars divided by age in days.</p></details><details><summary>Does it detect fake stars?</summary><p>No. It reports the counts the GitHub API returns. It only flags, with a published heuristic, new repos that look like cracked or pirated bundles and ranks them after all normal repos.</p></details><details><summary>Does it use generative AI?</summary><p>The rankings are computed by plain scripts. Translations of repo descriptions can be produced with an AI model in an optional step; numbers never are.</p></details><details><summary>How do I cite it?</summary><p>Use the CITATION.cff file in the repository (GitHub shows a Cite this repository button) and name the snapshot date.</p></details>
<h2>Documentation</h2>
<ul class="l">
<li><a href="https://github.com/jammyfu/github-risers/blob/main/docs/FACTS.md">Facts (one page)</a></li><li><a href="https://github.com/jammyfu/github-risers/blob/main/docs/FAQ.md">FAQ</a></li><li><a href="https://github.com/jammyfu/github-risers/blob/main/docs/CATEGORIES.md">Category rules</a></li><li><a href="https://github.com/jammyfu/github-risers/blob/main/docs/NEW_ARRIVALS.md">New-repo criteria</a></li><li><a href="https://github.com/jammyfu/github-risers/blob/main/docs/DAILY_UPDATE.md">Daily update</a></li><li><a href="https://raw.githubusercontent.com/jammyfu/github-risers/main/data/stats.json">Latest data (JSON)</a></li><li><a href="https://jammyfu.github.io/github-risers/llms.txt">llms.txt</a></li><li><a href="https://github.com/jammyfu/github-risers">Source code</a></li>
Expand Down
7 changes: 7 additions & 0 deletions docs/project-governance/ACCEPTANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,10 @@
- `CURRENT_PLAN.md` passes `python3 tools/next_plan.py --check` (one Plan ID, one Goal, 3-7 tasks, Out of Scope, Verification, Next Candidates)
- `tools/verify.py` and `tools/local_daily.sh` do not depend on the shell PATH or `shell=True` (`tests/test_harness.py`)
- The harness files from `continuous-project-loop` exist and `AGENTS.md` names the two CoderSkills skills


## Delayed daily schedules

- A scheduled run delayed beyond midnight refreshes when no data is committed for its current Los Angeles day.
- A second trigger on the same LA day skips after a successful commit, including across UTC date and DST boundaries.
- Bad/future scheduling state produces a failed job, not a green skip; manual dispatch stays unconditional.
Loading
Loading