diff --git a/.github/workflows/daily-update.yml b/.github/workflows/daily-update.yml index 632fb8e..d66f783 100644 --- a/.github/workflows/daily-update.yml +++ b/.github/workflows/daily-update.yml @@ -1,16 +1,14 @@ name: daily-update # Each run also stores its result under rankings/// (history) and latest/. -# Runs at Beijing 00:00 and America/Los_Angeles 00:00 every day. -# Beijing (UTC+8, no DST): 16:00 UTC -> '0 16 * * *' -# Los Angeles midnight: 07:00 UTC in PDT, 08:00 UTC in PST -> '0 7 * * *' and '0 8 * * *' -# GitHub cron is UTC-only, so the two LA entries both fire and tools/schedule_guard.py lets only the +# Runs once a day at America/Los_Angeles 00:00 (follows DST). +# 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. # If Actions is unavailable for the account, use tools/local_daily.sh (see docs/DAILY_UPDATE.md). on: schedule: - - cron: '0 16 * * *' - cron: '0 7 * * *' - cron: '0 8 * * *' workflow_dispatch: @@ -34,15 +32,10 @@ jobs: id: gate env: EVENT: ${{ github.event_name }} - CRON: ${{ github.event.schedule }} run: | - # Beijing slot and manual runs always proceed. The two LA slots proceed only at LA midnight. - # Output "slot" names the history folder: cn -> rankings//cn-0000, pt -> pt-0000, - # auto (manual dispatch) -> cn/pt if the clock happens to be at a slot, else manual-HHMM. - if [ "$EVENT" = "schedule" ] && [ "$CRON" = "0 16 * * *" ]; then - echo "run=true" >> "$GITHUB_OUTPUT" - echo "slot=cn" >> "$GITHUB_OUTPUT" - elif [ "$EVENT" = "schedule" ]; then + # Scheduled runs proceed only at Los Angeles midnight (history folder 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 echo "run=true" >> "$GITHUB_OUTPUT" echo "slot=pt" >> "$GITHUB_OUTPUT" diff --git a/CURRENT_PLAN.md b/CURRENT_PLAN.md index 8981d97..6ed7c8f 100644 --- a/CURRENT_PLAN.md +++ b/CURRENT_PLAN.md @@ -2,39 +2,38 @@ ## Plan ID -PLAN-004 +PLAN-005 ## Goal -Stop the recurring daily-update failures at the root: the scheduled run must validate the data it generated (hard errors fail, GitHub API timing drift warns) instead of running the unit-test suite against live data, and must survive rate limits and a moving `main`. +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. ## In Scope -- `tools/data_checks.py`, `tools/verify.py --daily`, `.github/workflows/daily-update.yml`, `tools/local_daily.sh` -- `tools/update_stars.py` API retry/backoff, `tools/risers_core.py` hard limit for history drift -- Tests in `tests/test_daily_robustness.py`; docs in `docs/DAILY_UPDATE.md` and governance files +- `.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 ## Out of Scope -- Ranking logic, README copy, cron times, secrets, merging to `main` +- Ranking logic, star-data fetch, translation pipeline, deleting historical `cn-0000` snapshots, merging to `main` ## Tasks -- [x] Group the failed runs by cause (41122642510, 41235403615) -- [x] Add `tools/data_checks.py` with error and warning severities and `verify.py --daily` -- [x] Wire the workflow and `local_daily.sh` to `--daily`; unit tests stay in `ci.yml` -- [x] Retry 429 / rate-limited 403 / 5xx / network errors; retry the push after rebasing onto `main` -- [x] Regression tests for all of the above -- [ ] Owner reviews the PR; merge only with approval +- [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 ## 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 commits data +- After merge, confirm the next scheduled `daily-update` run writes `rankings//pt-0000/` - Thin Developer Tools / Design / Data new-repo boards: see `TODO_BACKLOG.md` - Acceleration column (this week vs the week before) diff --git a/MASTER_PLAN.md b/MASTER_PLAN.md index 73fb38f..fdb1f8c 100644 --- a/MASTER_PLAN.md +++ b/MASTER_PLAN.md @@ -2,7 +2,7 @@ ## Goal -A trustworthy ranking of fast-rising GitHub repositories by stars gained per day, grouped by category (AI first), refreshed twice a day from the GitHub API and readable in five languages. +A trustworthy ranking of fast-rising GitHub repositories by stars gained per day, grouped by category (AI first), refreshed daily from the GitHub API and readable in five languages. ## Current phase @@ -15,7 +15,7 @@ Phase 4: harness and upkeep (Phases 1-3 are done and merged). ### Phase 2: Growth data and automation (done) - stars/day for 7/30/365 days from `stargazers/history` -- Daily update (Actions + local fallback) at Beijing 00:00 and Los Angeles 00:00, per-slot history, `latest/` +- Daily update (Actions + local fallback) at America/Los_Angeles 00:00, per-slot history, `latest/` - Top-20 boards, 7-day newcomers board ### Phase 3: Categories, i18n pipeline, discovery (done) @@ -30,7 +30,7 @@ Phase 4: harness and upkeep (Phases 1-3 are done and merged). ## Definition of done for a phase - `python3 tools/verify.py` passes; data in `data/stats.json` came from a real run -- A daily update has run unattended at both the Beijing and Los Angeles slots +- A daily update has run unattended at the Los Angeles slot ## Questions for next review diff --git a/PROJECT_BRIEF.md b/PROJECT_BRIEF.md index 85f3059..6f3ef2b 100644 --- a/PROJECT_BRIEF.md +++ b/PROJECT_BRIEF.md @@ -10,7 +10,7 @@ Open-source projects ranked by star growth speed (stars/day), grouped by categor ## Why -Total-star lists let old projects stay on top; newly rising projects get buried. All numbers are pulled from the GitHub API daily at Beijing and Los Angeles midnight. +Total-star lists let old projects stay on top; newly rising projects get buried. All numbers are pulled from the GitHub API daily. ## First goals diff --git a/README.ja.md b/README.ja.md index f960cc1..3835297 100644 --- a/README.ja.md +++ b/README.ja.md @@ -6,9 +6,9 @@ [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![CI](https://github.com/jammyfu/github-risers/actions/workflows/ci.yml/badge.svg)](https://github.com/jammyfu/github-risers/actions/workflows/ci.yml) [![updated](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjammyfu%2Fgithub-risers%2Fmain%2Fdata%2Fstats.json&query=%24.today_utc&label=updated&color=brightgreen)](latest/rankings.ja.md) -総スター数ではなく、1 日あたりに増えたスター数で GitHub リポジトリを並べます。AI が先頭で、開発者ツール、セキュリティ、Web / フロントエンド、インフラ、デザイン / クリエイティブ、データが続きます。1 日 2 回更新。 +総スター数ではなく、1 日あたりに増えたスター数で GitHub リポジトリを並べます。AI が先頭で、開発者ツール、セキュリティ、Web / フロントエンド、インフラ、デザイン / クリエイティブ、データが続きます。毎日更新。 -データ取得 2026-11-04 21:42(UTC)。1 日 2 回:北京時間 0 時(UTC+8)、ロサンゼルス時間 0 時(America/Los_Angeles)。 +データ取得 2026-11-04 21:42(UTC)。毎日更新。 [ウェブサイト](https://jammyfu.github.io/github-risers/) · [仕組み](docs/HOW_IT_WORKS.ja.md) · [ファクト](docs/FACTS.md) · [履歴](rankings/README.ja.md) · [最新](latest/rankings.ja.md) @@ -40,7 +40,7 @@ GitHub Trending は GitHub 公式のページです。GitHub Risers は独立し ### 更新は何回? -1 日 2 回、北京時間 0 時とロサンゼルス時間 0 時です。実行のたびに保存します。 [詳しく見る](docs/FAQ.md#how-often-is-it-updated) +毎日更新します。実行のたびに保存します。 [詳しく見る](docs/FAQ.md#how-often-is-it-updated) ### 数値はどう出す? diff --git a/README.ko.md b/README.ko.md index 07eb6b5..676bd32 100644 --- a/README.ko.md +++ b/README.ko.md @@ -6,9 +6,9 @@ [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![CI](https://github.com/jammyfu/github-risers/actions/workflows/ci.yml/badge.svg)](https://github.com/jammyfu/github-risers/actions/workflows/ci.yml) [![updated](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjammyfu%2Fgithub-risers%2Fmain%2Fdata%2Fstats.json&query=%24.today_utc&label=updated&color=brightgreen)](latest/rankings.ko.md) -총 스타 수가 아니라 하루에 늘어난 스타 수로 GitHub 저장소를 줄 세웁니다. AI가 맨 앞이고 개발자 도구, 보안, Web / 프론트엔드, 인프라, 디자인 / 크리에이티브, 데이터가 뒤따릅니다. 하루 두 번 갱신합니다. +총 스타 수가 아니라 하루에 늘어난 스타 수로 GitHub 저장소를 줄 세웁니다. AI가 맨 앞이고 개발자 도구, 보안, Web / 프론트엔드, 인프라, 디자인 / 크리에이티브, 데이터가 뒤따릅니다. 매일 갱신합니다. -데이터 시각 2026-11-04 21:42(UTC). 하루 두 번: 베이징 0시(UTC+8), 로스앤젤레스 0시(America/Los_Angeles). +데이터 시각 2026-11-04 21:42(UTC). 매일 갱신합니다. [웹사이트](https://jammyfu.github.io/github-risers/) · [작동 방식](docs/HOW_IT_WORKS.ko.md) · [팩트](docs/FACTS.md) · [지난 랭킹](rankings/README.ko.md) · [최신](latest/rankings.ko.md) @@ -40,7 +40,7 @@ GitHub Trending은 GitHub 공식 페이지입니다. GitHub Risers는 독립적 ### 얼마나 자주 갱신하나요? -하루 두 번, 베이징 시간 0시와 로스앤젤레스 시간 0시입니다. 실행할 때마다 보관합니다. [더 보기](docs/FAQ.md#how-often-is-it-updated) +매일 갱신합니다. 실행할 때마다 보관합니다. [더 보기](docs/FAQ.md#how-often-is-it-updated) ### 숫자는 어떻게 계산하나요? diff --git a/README.md b/README.md index eab5b35..f968b2a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Trending GitHub repos ranked by star growth (stars gained per day), not total stars: AI first, then developer tools, security, web, infrastructure, design and data. -Data from 2026-11-04 21:42 UTC. Twice a day: 00:00 Beijing (UTC+8) and 00:00 Los Angeles (America/Los_Angeles). +Data from 2026-11-04 21:42 UTC. Updated daily. [Website](https://jammyfu.github.io/github-risers/) · [How it works](docs/HOW_IT_WORKS.md) · [Facts](docs/FACTS.md) · [History](rankings/README.md) · [Latest](latest/rankings.md) @@ -40,7 +40,7 @@ GitHub Trending is GitHub's own page. GitHub Risers is an independent open-sourc ### How often is it updated? -Twice a day, at 00:00 Beijing time and 00:00 Los Angeles time. Every run is archived. [Read more](docs/FAQ.md#how-often-is-it-updated) +Updated daily. Every run is archived. [Read more](docs/FAQ.md#how-often-is-it-updated) ### How is the data computed? diff --git a/README.zh-Hans.md b/README.zh-Hans.md index 723dec2..79419fb 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -6,9 +6,9 @@ [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![CI](https://github.com/jammyfu/github-risers/actions/workflows/ci.yml/badge.svg)](https://github.com/jammyfu/github-risers/actions/workflows/ci.yml) [![updated](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjammyfu%2Fgithub-risers%2Fmain%2Fdata%2Fstats.json&query=%24.today_utc&label=updated&color=brightgreen)](latest/rankings.zh-Hans.md) -按每天新增星标(而不是总星标)给 GitHub 仓库排名:AI 在前,其后是开发者工具、安全、Web / 前端、基础设施、设计 / 创意和数据;每天更新两次。 +按每天新增星标(而不是总星标)给 GitHub 仓库排名:AI 在前,其后是开发者工具、安全、Web / 前端、基础设施、设计 / 创意和数据;每天更新。 -数据取自 2026-11-04 21:42(UTC)。每天两次:北京时间 0 点(UTC+8)、洛杉矶时间 0 点(America/Los_Angeles)。 +数据取自 2026-11-04 21:42(UTC)。每天更新。 [网站](https://jammyfu.github.io/github-risers/) · [运作方式](docs/HOW_IT_WORKS.zh-Hans.md) · [事实速查](docs/FACTS.md) · [历史榜单](rankings/README.zh-Hans.md) · [最新快照](latest/rankings.zh-Hans.md) @@ -40,7 +40,7 @@ GitHub Trending 是 GitHub 官方页面;GitHub 涨星榜是独立的开源项 ### 多久更新一次? -每天两次,北京时间 0 点和洛杉矶时间 0 点;每次运行都会存档。 [了解更多](docs/FAQ.md#how-often-is-it-updated) +每天更新;每次运行都会存档。 [了解更多](docs/FAQ.md#how-often-is-it-updated) ### 数据怎么算? diff --git a/README.zh-Hant.md b/README.zh-Hant.md index ffd9da7..ae6eac4 100644 --- a/README.zh-Hant.md +++ b/README.zh-Hant.md @@ -6,9 +6,9 @@ [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![CI](https://github.com/jammyfu/github-risers/actions/workflows/ci.yml/badge.svg)](https://github.com/jammyfu/github-risers/actions/workflows/ci.yml) [![updated](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjammyfu%2Fgithub-risers%2Fmain%2Fdata%2Fstats.json&query=%24.today_utc&label=updated&color=brightgreen)](latest/rankings.zh-Hant.md) -依每天新增星號(而不是總星號)替 GitHub 儲存庫排名:AI 在前,接著是開發者工具、資安、Web / 前端、基礎建設、設計 / 創意與資料;每天更新兩次。 +依每天新增星號(而不是總星號)替 GitHub 儲存庫排名:AI 在前,接著是開發者工具、資安、Web / 前端、基礎建設、設計 / 創意與資料;每天更新。 -資料取自 2026-11-04 21:42(UTC)。每天兩次:北京時間 0 點(UTC+8)、洛杉磯時間 0 點(America/Los_Angeles)。 +資料取自 2026-11-04 21:42(UTC)。每天更新。 [網站](https://jammyfu.github.io/github-risers/) · [運作方式](docs/HOW_IT_WORKS.zh-Hant.md) · [事實速查](docs/FACTS.md) · [歷史榜單](rankings/README.zh-Hant.md) · [最新快照](latest/rankings.zh-Hant.md) @@ -40,7 +40,7 @@ GitHub Trending 是 GitHub 官方頁面;GitHub 漲星榜是獨立的開源專 ### 多久更新一次? -每天兩次,北京時間 0 點和洛杉磯時間 0 點;每次執行都會存檔。 [了解更多](docs/FAQ.md#how-often-is-it-updated) +每天更新;每次執行都會存檔。 [了解更多](docs/FAQ.md#how-often-is-it-updated) ### 資料怎麼算? diff --git a/TODO_BACKLOG.md b/TODO_BACKLOG.md index 7a4f908..4817b26 100644 --- a/TODO_BACKLOG.md +++ b/TODO_BACKLOG.md @@ -2,8 +2,7 @@ ## Now -- [ ] After the daily-update reliability PR (PLAN-004) is merged, confirm the next scheduled `daily-update` run commits data (Verify must only fail on real data errors) -- [ ] Confirm the first unattended cron runs after the merge: `cn-0000` (Beijing) and `pt-0000` (Los Angeles) folders appear under `rankings//` (`gh run list --workflow daily-update.yml`) +- [ ] After the once-daily US schedule PR (PLAN-005) is merged, confirm the next scheduled `daily-update` run writes `rankings//pt-0000/` (`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) diff --git a/docs/DAILY_UPDATE.md b/docs/DAILY_UPDATE.md index ff3a150..e16524b 100644 --- a/docs/DAILY_UPDATE.md +++ b/docs/DAILY_UPDATE.md @@ -7,7 +7,7 @@ project that cannot be fetched is listed under "Missing data" in the README. 1. `GET /repos/{owner}/{repo}` → `stargazers_count`, `created_at`. 2. `GET /repos/{owner}/{repo}/stargazers/history` (newest week first, 30 weeks per page) until one year is covered. -3. Re-discovers the "hottest new repos (last 7 days)" ranking with the GitHub search API (about 30 search requests, paced to the 30/min limit, so a run takes roughly 2 minutes; rules in [NEW_ARRIVALS.md](NEW_ARRIVALS.md)). Every slot run (Beijing and Los Angeles, Actions or local) redoes it from scratch. +3. Re-discovers the "hottest new repos (last 7 days)" ranking with the GitHub search API (about 30 search requests, paced to the 30/min limit, so a run takes roughly 2 minutes; rules in [NEW_ARRIVALS.md](NEW_ARRIVALS.md)). Every production run (Los Angeles midnight, Actions or local) redoes it from scratch. 4. Writes `data/stats.json` (growth per project), archives it under `rankings///` and `latest/`, appends today's star count to `data/stars.json` (snapshots, used for cross-checks over time), then regenerates the five READMEs. @@ -25,8 +25,8 @@ stars, UTC vs bucket boundaries). Slack was 2 until 2026-11-01, when ### One code path for every scheduled run -The Beijing 00:00 cron (`0 16 * * *`), both Los Angeles crons (`0 7`/`0 8 * * *`, gated by `tools/schedule_guard.py`), -`workflow_dispatch`, and the local launchd jobs (`docs/launchd/*.plist` -> `tools/local_daily.sh`) all end in the same +Both Los Angeles crons (`0 7`/`0 8 * * *`, gated by `tools/schedule_guard.py`), +`workflow_dispatch`, and the local launchd job (`docs/launchd/*.plist` -> `tools/local_daily.sh`) all end in the same command: `python3 tools/update_stars.py --slot `. That calls `newcomers.discover_all` -> `newcomers.discover_category`, so the topic-only category rules, the suspected-crack/pirated ordering (`tools/spam.py`, `newcomers.rank_items`) and the leader exclusion (`render_readme.leaders`) apply to every run. `tests/test_spam.py::ScheduledPathTests` guards this. @@ -36,8 +36,8 @@ leader exclusion (`render_readme.leaders`) apply to every run. `tests/test_spam. Every run stores its result in its own folder and keeps it (nothing is overwritten except a re-run of the same slot): ``` -rankings//cn-0000/ Beijing 00:00 run (date = Asia/Shanghai date) -rankings//pt-0000/ Los Angeles 00:00 run (date = America/Los_Angeles date) +rankings//pt-0000/ Los Angeles 00:00 run (date = America/Los_Angeles date) ← production +rankings//cn-0000/ optional Beijing 00:00 run (date = Asia/Shanghai date; kept for history) rankings//manual-HHMM/ any other run, e.g. manual dispatch (date/time = UTC) stats.json full API-derived data of that run, plus a "slot" block and the new-repos ranking (`newcomers`) rankings.md top-20 tables for that run, new-repos table first (English, compact) @@ -49,8 +49,9 @@ data/stats.json what the READMEs are rendered from (same as the newest run) The five READMEs show the latest ranking and link to the history index and `latest/rankings*.md` of their own language. The pages are generated from `tools/i18n.py` (strings) and `stats.json` (data); never edit them by hand. -`tools/update_stars.py --slot cn|pt|manual|auto` picks the folder (`auto`, the default, uses cn if it is 00:xx in -Beijing, pt if 00:xx in Los Angeles, otherwise manual). The workflow and `tools/local_daily.sh` pass the slot themselves. +`tools/update_stars.py --slot cn|pt|manual|auto` picks the folder (`auto`, the default, uses pt if it is 00:xx in +Los Angeles, cn if 00:xx in Beijing, otherwise manual). The scheduled workflow always passes `pt`; `tools/local_daily.sh` +passes the slot itself. `tools/verify.py` checks that `latest/`, the indexes and every snapshot's `rankings*.md` (all languages) match their data. ## New-repo description translations and polish @@ -63,17 +64,16 @@ Since the i18n pipeline, `tools/update_stars.py` fills this cache itself: after translates every displayed description that has no current translation (already translated ones are skipped), then renders. In CI this can use the Cursor CLI and the optional repository secret `CURSOR_API_KEY`; this repo has no such secret on purpose, so the Actions run skips translation and carries on with the existing texts. The translation is done by the -owner's Mac: a launchd job (`tools/com.jammyfu.risers-i18n.plist`, Beijing 01:15 and 17:15, ~1h after the Actions slots) runs +owner's Mac: a launchd job (`tools/com.jammyfu.risers-i18n.plist`, Beijing 17:15, ~1–2h after the Los Angeles midnight Actions slot) runs `tools/mac_i18n_cron.sh`, which pulls, translates what is missing with the signed-in Antigravity CLI (`agy`), renders, verifies, commits `chore(i18n): ...` and pushes to main. Log: `~/Library/Logs/risers-i18n.log`. Polishing the copy is done on the owner's machine with the Antigravity CLI (`tools/polish_local.sh`, or `tools/local_daily.sh`), never in CI. Details, guards and the manual commands are in [I18N_PIPELINE.md](I18N_PIPELINE.md). `python3 tools/newcomer_i18n.py` still lists the gaps. -## Schedule: Beijing 00:00 and Los Angeles 00:00 +## Schedule: once a day at Los Angeles 00:00 | Slot | When | cron (UTC) | |---|---|---| -| Beijing 00:00 (UTC+8, no DST) | 16:00 UTC | `0 16 * * *` | | 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 * * *` | @@ -81,10 +81,10 @@ GitHub cron only knows UTC, so both LA entries fire every day. `tools/schedule_g 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`). -The Beijing entry is unguarded. Manual `workflow_dispatch` always runs. +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. The Beijing slot and the next day's runs still refresh the data, so a delay never leaves it stale for more than a day. +would skip until the next day's slot. Use `workflow_dispatch` (or `tools/local_daily.sh`) if a day needs a manual refresh. ## Option A: GitHub Actions (preferred when it runs) @@ -129,8 +129,8 @@ gh api repos/jammyfu/github-risers/check-runs//annotations --jq '.[].mes GITHUB_TOKEN=$(gh auth token) python3 tools/update_stars.py # fetch + render only tools/local_daily.sh # update, verify (--daily), commit, push to main (any time) tools/local_daily.sh --no-push # update, verify, commit locally -tools/local_daily.sh --slot beijing # only proceeds if it is 00:xx in Asia/Shanghai tools/local_daily.sh --slot la # only proceeds if it is 00:xx in America/Los_Angeles (DST-aware) +tools/local_daily.sh --slot beijing # optional; only proceeds if it is 00:xx in Asia/Shanghai ``` `tools/local_daily.sh` reads `GITHUB_TOKEN` (falls back to `gh auth token`), refuses to run off `main` or @@ -140,28 +140,24 @@ with `skip: ...` without touching anything. `ATLAS_NOW_UTC=2026-10-29T08:30:00Z` ### cron (Linux / macOS, machine clock in Beijing time) ```cron -0 0 * * * cd /path/to/github-risers && tools/local_daily.sh --slot beijing >> /tmp/risers-daily.log 2>&1 0 15,16 * * * cd /path/to/github-risers && tools/local_daily.sh --slot la >> /tmp/risers-daily.log 2>&1 ``` Los Angeles midnight is 15:00 Beijing time in PDT and 16:00 in PST; the `la` slot fires at both and the guard picks the right one. -If the machine uses another time zone, run the `la` line hourly (`0 * * * *`) and the guard does the rest; for Beijing use -`CRON_TZ=Asia/Shanghai` where your cron supports it, or hourly with `--slot beijing`. +If the machine uses another time zone, run the `la` line hourly (`0 * * * *`) and the guard does the rest. Do not commit a token. Put `GITHUB_TOKEN=...` at the top of the crontab, or leave it unset and rely on `gh auth login`. ### launchd (macOS, machine clock in Beijing time) -Two agents: `docs/launchd/com.jammyfu.github-risers.beijing.plist` (00:00) and -`docs/launchd/com.jammyfu.github-risers.la.plist` (15:00 and 16:00, guarded). Replace `/PATH/TO` with the clone path, then: +One agent: `docs/launchd/com.jammyfu.github-risers.la.plist` (15:00 and 16:00, guarded). Replace `/PATH/TO` with the clone path, then: ```bash -cp docs/launchd/com.jammyfu.github-risers.*.plist ~/Library/LaunchAgents/ -launchctl load ~/Library/LaunchAgents/com.jammyfu.github-risers.beijing.plist +cp docs/launchd/com.jammyfu.github-risers.la.plist ~/Library/LaunchAgents/ launchctl load ~/Library/LaunchAgents/com.jammyfu.github-risers.la.plist launchctl start com.jammyfu.github-risers.la # test: prints "skip: ..." unless it is LA midnight ``` -They rely on `gh auth login` for the token (launchd has no shell profile). +It relies on `gh auth login` for the token (launchd has no shell profile). ## Verify diff --git a/docs/FACTS.md b/docs/FACTS.md index 17e07de..b555ae8 100644 --- a/docs/FACTS.md +++ b/docs/FACTS.md @@ -16,7 +16,7 @@ A one-page, quotable summary. Details live in [CATEGORIES.md](CATEGORIES.md), [N | New-repo hotness | `stars / max(age in days, 1)`, repos created in the last 7 days, at least 100 stars, not a fork, not archived | | Star data source | GitHub REST API only: `GET /repos/{owner}/{repo}` for the total and `GET /repos/{owner}/{repo}/stargazers/history` for daily gains. Missing data is shown as missing, never filled in | | New-repo discovery | GitHub search API, one `topic:` query per category topic; the repo must carry a category topic itself | -| Update schedule | Twice a day: 00:00 Beijing time (UTC+8) and 00:00 Los Angeles time (follows daylight saving). Runs by GitHub Actions cron, so a run can start a few minutes late | +| Update schedule | Daily. Runs by GitHub Actions, so a run can start a few minutes late | | History | Every run is kept under `rankings///`; the newest copy is in `latest/` | | Languages | English, 简体中文, 繁體中文, 日本語, 한국어 | | Suspected cracked / pirated repos | Flagged by a published heuristic (`tools/spam.py`) and ranked after all normal repos in the new-repo board; never a category leader; never hidden | diff --git a/docs/FAQ.md b/docs/FAQ.md index 9789ad7..dad5202 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,7 +1,7 @@ # GitHub Risers: FAQ ## What is GitHub Risers? -A leaderboard of GitHub repositories ranked by how fast they gain stars, not by how many they already have. AI is the main board; Developer Tools, Security, Web / Frontend, Infrastructure, Design / Creative and Data have their own boards. It is generated by scripts from the GitHub API and updated twice a day. +A leaderboard of GitHub repositories ranked by how fast they gain stars, not by how many they already have. AI is the main board; Developer Tools, Security, Web / Frontend, Infrastructure, Design / Creative and Data have their own boards. It is generated by scripts from the GitHub API and updated daily. ## How is it different from sorting by total stars? Total-star lists keep the oldest giants on top. GitHub Risers divides stars gained in a window (7, 30 or 365 days) by the number of days, so a project that is taking off now can outrank one that is merely famous. Total stars still appear, as the last board in each category. @@ -13,7 +13,7 @@ No. GitHub Trending is GitHub's own page. GitHub Risers is an independent, open- `per_day = stars gained over the window / days used`. The gain is the sum of daily star counts from `GET /repos/{owner}/{repo}/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". See [FACTS.md](FACTS.md). ## How often is it updated? -Twice a day, at 00:00 Beijing time (UTC+8) and 00:00 Los Angeles time. The schedule runs on GitHub Actions cron, so a run can start a few minutes late. Each run is archived in its own folder under `rankings/`. +Updated daily. Each run is archived in its own folder under `rankings/`. ## Where do the numbers come from? Are any estimated? Only the GitHub API. A project that cannot be fetched is shown as missing data; numbers are never typed in or estimated. diff --git a/docs/HOW_IT_WORKS.ja.md b/docs/HOW_IT_WORKS.ja.md index 66bd3b7..e8e2df9 100644 --- a/docs/HOW_IT_WORKS.ja.md +++ b/docs/HOW_IT_WORKS.ja.md @@ -6,7 +6,7 @@ 総スター数順のリストでは古い大物が居座り、いま伸びているものが埋もれます。GitHub Risers は 1 日あたりの増加スター数(直近 7・30・365 日)で並べ、カテゴリごとに表示します。AI がメインのランキングです。各カテゴリは、直近 7 日以内に作られたリポジトリの「新着」ランキング、週・月・年の増加ランキング、最後に総スター数の順です。数値はすべて GitHub API から取得しています。分類ルール:[docs/CATEGORIES.md](CATEGORIES.md)。 -1 日 2 回更新:北京時間 0 時(UTC+8)とロサンゼルス時間 0 時(America/Los_Angeles、夏時間に追従)。更新ごとに時間帯別に保存されます。 +毎日更新。更新ごとに保存されます。 README では各ランキングの上位 10 件を表示します。各ランキングの上位 20 件の全体(収録 70 プロジェクトから)は latest/ のファイルにあります。 diff --git a/docs/HOW_IT_WORKS.ko.md b/docs/HOW_IT_WORKS.ko.md index c8d0902..4a6bbfb 100644 --- a/docs/HOW_IT_WORKS.ko.md +++ b/docs/HOW_IT_WORKS.ko.md @@ -6,7 +6,7 @@ 총 스타 순 목록에서는 오래된 대형 프로젝트가 계속 상위를 차지하고, 지금 뜨는 프로젝트는 묻힙니다. GitHub Risers는 하루 평균 늘어난 스타 수(최근 7·30·365일)로 순위를 매기고 카테고리별로 보여 주며, AI가 메인 랭킹입니다. 각 카테고리는 최근 7일 안에 만들어진 저장소의 신규 랭킹, 주·월·연 증가 랭킹, 마지막으로 총 스타 순입니다. 모든 수치는 GitHub API에서 가져옵니다. 분류 규칙: [docs/CATEGORIES.md](CATEGORIES.md). -하루 두 번 갱신: 베이징 시간 0시(UTC+8)와 로스앤젤레스 시간 0시(America/Los_Angeles, 서머타임 반영). 갱신할 때마다 시간대별로 따로 보관합니다. +매일 갱신합니다. 갱신할 때마다 보관합니다. README에는 랭킹마다 상위 10개를 싣습니다. 각 랭킹의 상위 20개 전체(70개 수록 프로젝트 기준)는 latest/ 파일에 있습니다. diff --git a/docs/HOW_IT_WORKS.md b/docs/HOW_IT_WORKS.md index 628e27c..40a2b17 100644 --- a/docs/HOW_IT_WORKS.md +++ b/docs/HOW_IT_WORKS.md @@ -6,7 +6,7 @@ Total-star lists keep the old giants on top and bury what is taking off right now. GitHub Risers ranks repos by stars gained per day (last 7, 30 and 365 days) and groups them into categories, with AI as the main board. Every category has a hottest-new-repos board for repos created in the last 7 days, then week, month and year star-growth rankings, and total stars last. All numbers come straight from the GitHub API. Classification rules: [docs/CATEGORIES.md](CATEGORIES.md). -Updated twice a day: 00:00 Beijing time (UTC+8) and 00:00 Los Angeles time (America/Los_Angeles, follows daylight saving). Each update is archived under its own time slot. +Updated daily. Each update is archived. Each board in the README shows the top 10. The full top 20 of each board (from 70 tracked projects) is in the latest/ files. diff --git a/docs/HOW_IT_WORKS.zh-Hans.md b/docs/HOW_IT_WORKS.zh-Hans.md index cc4f8e5..583fca7 100644 --- a/docs/HOW_IT_WORKS.zh-Hans.md +++ b/docs/HOW_IT_WORKS.zh-Hans.md @@ -6,7 +6,7 @@ 多数榜单按总星标数排序,老项目长期占据榜首,正在爆发的新项目反而被埋没。GitHub 飙升榜按“每天新增多少星标”来排:统计近 7 天、30 天和 365 天的日均涨星,并按分类展示,AI 是默认置顶的主榜。每个分类先看“7 天新晋”榜(最近 7 天内创建的仓库),再看周、月、年涨星榜,总星标榜放在最后。数据全部来自 GitHub API。分类规则见 [docs/CATEGORIES.md](CATEGORIES.md)。 -每天更新两次:北京时间 0 点(UTC+8)和美西洛杉矶时间 0 点(America/Los_Angeles,随夏令时变化)。每次更新都会按各自的时段单独存档。 +每天更新。每次更新都会存档。 README 里每张榜只列前 10 名;每张榜完整的前 20 名(来自 70 个已收录项目)在 latest/ 文件里。 diff --git a/docs/HOW_IT_WORKS.zh-Hant.md b/docs/HOW_IT_WORKS.zh-Hant.md index 1d50013..27510de 100644 --- a/docs/HOW_IT_WORKS.zh-Hant.md +++ b/docs/HOW_IT_WORKS.zh-Hant.md @@ -6,7 +6,7 @@ 多數榜單依總星號數排序,老專案長期霸占榜首,正在爆發的新專案反而被埋沒。GitHub 飆升榜依「每天新增多少星號」來排:統計近 7 天、30 天和 365 天的日均漲星,並依分類呈現,AI 是預設置頂的主榜。每個分類先看「7 天新晉」榜(最近 7 天內建立的儲存庫),再看週、月、年漲星榜,總星號榜放在最後。資料全部來自 GitHub API。分類規則請見 [docs/CATEGORIES.md](CATEGORIES.md)。 -每天更新兩次:北京時間 0 點(UTC+8)和美西洛杉磯時間 0 點(America/Los_Angeles,隨夏令時間變動)。每次更新都會依各自的時段單獨存檔。 +每天更新。每次更新都會存檔。 README 裡每張榜只列前 10 名;每張榜完整的前 20 名(來自 70 個已收錄專案)在 latest/ 檔案裡。 diff --git a/docs/NEW_ARRIVALS.md b/docs/NEW_ARRIVALS.md index e14576a..c45ae6a 100644 --- a/docs/NEW_ARRIVALS.md +++ b/docs/NEW_ARRIVALS.md @@ -1,7 +1,7 @@ # Hottest new repos (created in the last 7 days) The first board inside every category. Everything comes from the GitHub API; nothing is estimated or hand-picked. -It is rebuilt on every run (Beijing 00:00 and Los Angeles 00:00), stored in the slot folder +It is rebuilt on every daily run, stored in the slot folder `rankings///` and copied to `latest/` like the other rankings. Code: `tools/newcomers.py`. Category rules: [CATEGORIES.md](CATEGORIES.md). diff --git a/docs/launchd/com.jammyfu.github-risers.beijing.plist b/docs/launchd/com.jammyfu.github-risers.beijing.plist deleted file mode 100644 index 3d7e855..0000000 --- a/docs/launchd/com.jammyfu.github-risers.beijing.plist +++ /dev/null @@ -1,23 +0,0 @@ - - - - - Label - com.jammyfu.github-risers.beijing - ProgramArguments - - /bin/bash - /PATH/TO/github-risers/tools/local_daily.sh - --slot - beijing - - StartCalendarInterval - - Hour0Minute0 - - StandardOutPath - /tmp/com.jammyfu.github-risers.beijing.log - StandardErrorPath - /tmp/com.jammyfu.github-risers.beijing.err.log - - diff --git a/docs/project-governance/ACCEPTANCE.md b/docs/project-governance/ACCEPTANCE.md index 8e0cf02..193eda4 100644 --- a/docs/project-governance/ACCEPTANCE.md +++ b/docs/project-governance/ACCEPTANCE.md @@ -15,14 +15,14 @@ - Categories in `data/categories.json` (ai primary + 6 others), each with newcomers / week / month / year / total, total last - New repos are found by topic only and must carry a category topic; no generic topics (`tests/test_spam.py`) - Suspected cracked / pirated repos are listed after every normal repo, marked ⚠, and never appear in "Category leaders" -- Every scheduled entry (both Beijing and Los Angeles crons, `workflow_dispatch`, local launchd) runs `tools/update_stars.py` (`tests/test_spam.py::ScheduledPathTests`) +- Every scheduled entry (Los Angeles crons, `workflow_dispatch`, local launchd) runs `tools/update_stars.py` (`tests/test_spam.py::ScheduledPathTests`) - README is a first screen (top 10 per board); the full top 20 is in `latest/`; GEO files exist and are linked ## README update date - The `updated` badge is dynamic (`shields.io` JSON → `data/stats.json` `today_utc`); i18n templates contain no `YYYY-MM-DD` - The stamp line is written from `stats.generated_at` on every `python3 tools/render_readme.py` run -- First screen states what the list is, the Beijing / Los Angeles schedule, how numbers are computed, and how to use or contribute +- First screen states what the list is, that it updates daily, how numbers are computed, and how to use or contribute ## Phase 4: harness diff --git a/docs/project-governance/CHANGELOG.md b/docs/project-governance/CHANGELOG.md index 0fc037f..7c7bb4e 100644 --- a/docs/project-governance/CHANGELOG.md +++ b/docs/project-governance/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG.md +## 2026-11-06 (once-daily US schedule) + +- The ranking list updates once a day at America/Los_Angeles midnight (`0 7 * * *` / `0 8 * * *` UTC, DST-aware via `tools/schedule_guard.py`). The Beijing 00:00 cron is no longer a production slot. +- README, FAQ, FACTS and HOW_IT_WORKS only say the list is updated daily. Exact clocks remain in `docs/DAILY_UPDATE.md`. + ## 2026-11-01 (daily-update reliability) - The daily run no longer runs the unit tests against live data. `python3 tools/verify.py --daily` validates the generated data (`tools/data_checks.py`): real errors fail, GitHub API timing drift only warns. CI still runs every unit test on pull requests. diff --git a/docs/project-governance/DECISIONS.md b/docs/project-governance/DECISIONS.md index 3118337..6737d6b 100644 --- a/docs/project-governance/DECISIONS.md +++ b/docs/project-governance/DECISIONS.md @@ -84,3 +84,11 @@ - It now reads `$.today_utc` from `data/stats.json` on `main` via shields.io dynamic JSON. When the daily job writes a new `today_utc`, the badge refreshes without anyone editing the README by hand. - The stamp line still comes from `stats.generated_at` on every `tools/render_readme.py` run (the same path the daily-update workflow uses). Templates in `tools/i18n.py` contain `{ts}` only; no `YYYY-MM-DD` is typed in. - First-screen FAQ grew by two items (how the numbers are computed; how to use or contribute). They link existing `docs/FAQ.md` anchors; the GEO FAQ file is not generated. + +## 2026-11-06 + +### Once a day at America/Los_Angeles midnight + +- Production cadence is one ranking update per day, not Beijing 00:00 plus Los Angeles 00:00. +- GitHub cron is still UTC-only, so both `0 7 * * *` (PDT) and `0 8 * * *` (PST) stay; `tools/schedule_guard.py` lets exactly one through. Historical `cn-0000` snapshots are kept; new scheduled runs write `pt-0000`. +- Public README / FAQ / FACTS / HOW_IT_WORKS copy only says the list is updated daily. Exact clocks stay in `docs/DAILY_UPDATE.md` for operators. diff --git a/docs/project-governance/WORKLOG.md b/docs/project-governance/WORKLOG.md index 7c6eea0..ed464f9 100644 --- a/docs/project-governance/WORKLOG.md +++ b/docs/project-governance/WORKLOG.md @@ -1,5 +1,11 @@ # WORKLOG.md +## 2026-11-06 + +- PLAN-005: production cadence is once daily at America/Los_Angeles 00:00. Dropped the Beijing cron (`0 16 * * *`) and the Beijing launchd agent. Scheduled Actions runs always use slot `pt`. +- Public copy (stamp / a3 / schedule / tagline, FAQ, FACTS) no longer names cron clocks or the dual Beijing+PT schedule; operator clocks stay in `docs/DAILY_UPDATE.md`. +- i18n Mac job is once a day at Beijing 17:15 (~1–2h after LA midnight). `--slot beijing` remains available for optional local runs; historical `cn-0000` snapshots are untouched. + ## 2026-11-01 - Daily-update run 41235403615 (Beijing ~03:41, 2026-11-01) fetched OK then failed Verify: `affaan-m/ECC` had `|history_total - stars| = 3` while the allowed slack was 2, so commit/push was skipped. diff --git a/tests/test_repo.py b/tests/test_repo.py index 3967c08..b8245cc 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -148,10 +148,19 @@ def test_every_readme_has_schedule_and_leaders_from_real_data(self): for code, (fname, _l) in LANGS.items(): s = render_readme.S[code] text = (ROOT / fname).read_text("utf-8") - self.assertIn("UTC+8", s["schedule"]) # the full schedule lives on the how-it-works page - self.assertIn("America/Los_Angeles", s["schedule"]) + # Public copy only says it updates daily; clocks live in operator docs, not README / HOW_IT_WORKS. + self.assertRegex(s["stamp"], r"daily|每天更新|毎日更新|매일 갱신") + self.assertRegex(s["a3"], r"daily|每天更新|毎日更新|매일 갱신") + self.assertRegex(s["schedule"], r"daily|每天更新|毎日更新|매일 갱신") + for gone in ("UTC+8", "00:00", "Beijing", "Los Angeles", "twice", "两次", "兩次", + "1 日 2 回", "하루 두 번", "America/Los_Angeles"): + self.assertNotIn(gone, s["stamp"], code) + self.assertNotIn(gone, s["a3"], code) + self.assertNotIn(gone, s["schedule"], code) how = (ROOT / "docs" / render_readme.how_name(code)).read_text("utf-8") self.assertIn(s["schedule"], how, code) + self.assertNotIn("UTC+8", text, fname) + self.assertNotIn("UTC+8", how, code) self.assertIn(f"docs/{render_readme.how_name(code)}", text, fname) # README links to it sec = text.split(f"## {s['tops_title']}\n", 1)[1].split("\n## ", 1)[0] rows = [l for l in sec.splitlines() if l.startswith("| **")] diff --git a/tests/test_schedule.py b/tests/test_schedule.py index 7a504d0..389f293 100644 --- a/tests/test_schedule.py +++ b/tests/test_schedule.py @@ -94,17 +94,17 @@ def test_unknown_slot_rejected(self): class WorkflowScheduleTests(unittest.TestCase): wf = (ROOT / ".github/workflows/daily-update.yml").read_text("utf-8") - def test_three_crons(self): + def test_two_la_crons(self): crons = re.findall(r"- cron: '([^']+)'", self.wf) - self.assertEqual(sorted(crons), sorted(["0 16 * * *", "0 7 * * *", "0 8 * * *"])) + self.assertEqual(sorted(crons), sorted(["0 7 * * *", "0 8 * * *"])) + self.assertNotIn("0 16 * * *", self.wf) def test_la_guard_wired_and_steps_gated(self): self.assertIn("tools/schedule_guard.py --tz America/Los_Angeles --hour 0", self.wf) self.assertEqual(self.wf.count("if: steps.gate.outputs.run == 'true'"), 4) # install CLI, fetch, verify, commit - # Beijing cron is the only unguarded schedule - self.assertIn('"$CRON" = "0 16 * * *"', self.wf) - self.assertIn("slot=cn", self.wf) + self.assertNotIn("slot=cn", self.wf) # scheduled production is pt only self.assertIn("slot=pt", self.wf) + self.assertIn("slot=auto", self.wf) self.assertIn('--slot "$SLOT"', self.wf) self.assertIn("git add data rankings latest README*.md", self.wf) diff --git a/tests/test_spam.py b/tests/test_spam.py index 7462c72..413a72d 100644 --- a/tests/test_spam.py +++ b/tests/test_spam.py @@ -174,12 +174,13 @@ def test_workflow_and_local_script_run_update_stars(self): sh = (ROOT / "tools/local_daily.sh").read_text("utf-8") for text in (wf, sh): self.assertRegex(text, r"tools/update_stars\.py --slot") - # both Beijing and both LA cron entries feed the single "update" job that runs update_stars.py + # both LA cron entries feed the single "update" job that runs update_stars.py self.assertEqual(len(re.findall(r"update_stars\.py", wf)), 1) - for cron in ("'0 16 * * *'", "'0 7 * * *'", "'0 8 * * *'"): + for cron in ("'0 7 * * *'", "'0 8 * * *'"): self.assertIn(cron, wf) + self.assertNotIn("'0 16 * * *'", wf) plists = list((ROOT / "docs/launchd").glob("*.plist")) - self.assertEqual(len(plists), 2) # Beijing 00:00 and Los Angeles 00:00 + self.assertEqual([p.name for p in plists], ["com.jammyfu.github-risers.la.plist"]) for pl in plists: self.assertIn("local_daily.sh", pl.read_text("utf-8"), pl.name) diff --git a/tools/com.jammyfu.risers-i18n.plist b/tools/com.jammyfu.risers-i18n.plist index cdc4464..b3e7624 100644 --- a/tools/com.jammyfu.risers-i18n.plist +++ b/tools/com.jammyfu.risers-i18n.plist @@ -2,7 +2,7 @@ + The Mac's clock is Beijing time. 17:15 is ~1h (PST) to ~2h (PDT) after America/Los_Angeles midnight. --> Labelcom.jammyfu.risers-i18n @@ -13,7 +13,6 @@ StartCalendarInterval - Hour1Minute15 Hour17Minute15 StandardOutPath/Users/jammyfu/Library/Logs/risers-i18n.log diff --git a/tools/i18n.py b/tools/i18n.py index 2b83128..fb2df2a 100644 --- a/tools/i18n.py +++ b/tools/i18n.py @@ -41,7 +41,7 @@ "q2": "How is it different from GitHub Trending?", "a2": "GitHub Trending is GitHub's own page. GitHub Risers is an independent open-source project with published rules, five languages and a saved history of every update. It is not affiliated with GitHub.", "q3": "How often is it updated?", - "a3": "Twice a day, at 00:00 Beijing time and 00:00 Los Angeles time. Every run is archived.", + "a3": "Updated daily. Every run is archived.", "q4": "How is the data computed?", "a4": "Stars gained in the last 7, 30 or 365 complete UTC days, divided by those days. All numbers come from the GitHub API; nothing is typed or estimated.", "q5": "How do I use or contribute?", @@ -49,13 +49,13 @@ "more": "Read more", "star_ask": "If this saved you time, give the repo a star so others can find it.", "cite": "How to cite: see [CITATION.cff](CITATION.cff).", - "stamp": "Data from {ts} UTC. Twice a day: 00:00 Beijing (UTC+8) and 00:00 Los Angeles (America/Los_Angeles).", + "stamp": "Data from {ts} UTC. Updated daily.", "footer": "How the numbers are produced and how to contribute: [{how}]({how}). New-repo rules: [docs/NEW_ARRIVALS.md](docs/NEW_ARRIVALS.md). Categories: [docs/CATEGORIES.md](docs/CATEGORIES.md). Pull requests: [CONTRIBUTING.md](CONTRIBUTING.md).", "doc_title": "How GitHub Risers works", "doc_back": "← Back to the rankings", "intro": "Total-star lists keep the old giants on top and bury what is taking off right now. GitHub Risers ranks repos by stars gained per day (last 7, 30 and 365 days) and groups them into categories, with AI as the main board. Every category has a hottest-new-repos board for repos created in the last 7 days, then week, month and year star-growth rankings, and total stars last. All numbers come straight from the GitHub API. Classification rules: [docs/CATEGORIES.md](docs/CATEGORIES.md).", "cat_title": "Categories", - "schedule": "Updated twice a day: 00:00 Beijing time (UTC+8) and 00:00 Los Angeles time (America/Los_Angeles, follows daylight saving). Each update is archived under its own time slot.", + "schedule": "Updated daily. Each update is archived.", "tops_title": "Category leaders this round", "tops_intro": "The #1 of every board in every category, generated by script from the data below. n/a means that board has no entry in this run.", "tops_h_cat": "Category", @@ -120,7 +120,7 @@ "history": "下方表格为最新榜单。每次更新还会按时段另存一份:[榜单历史]({index}) · [{latest}]({latest})。", "shown": "README 里每张榜只列前 {m} 名;每张榜完整的前 {n} 名(来自 {total} 个已收录项目)在 latest/ 文件里。", "title": "GitHub 涨星榜 / GitHub Risers", - "tagline": "按每天新增星标(而不是总星标)给 GitHub 仓库排名:AI 在前,其后是开发者工具、安全、Web / 前端、基础设施、设计 / 创意和数据;每天更新两次。", + "tagline": "按每天新增星标(而不是总星标)给 GitHub 仓库排名:AI 在前,其后是开发者工具、安全、Web / 前端、基础设施、设计 / 创意和数据;每天更新。", "website": "网站", "links": "[{website}]({site}) · [运作方式]({how}) · [事实速查](docs/FACTS.md) · [历史榜单]({index}) · [最新快照]({latest})", "top_note": "这里每张榜只列前 10 名,完整的前 20 名见 [{latest}]({latest})。", @@ -139,7 +139,7 @@ "q2": "和 GitHub Trending 有什么不同?", "a2": "GitHub Trending 是 GitHub 官方页面;GitHub 涨星榜是独立的开源项目,规则公开,有五种语言,并保存每次更新的历史。它与 GitHub 官方没有关联。", "q3": "多久更新一次?", - "a3": "每天两次,北京时间 0 点和洛杉矶时间 0 点;每次运行都会存档。", + "a3": "每天更新;每次运行都会存档。", "q4": "数据怎么算?", "a4": "取最近 7、30 或 365 个完整 UTC 日的新增星标,再除以天数。数字全部来自 GitHub API,没有手填或估算。", "q5": "怎么看、怎么参与?", @@ -147,13 +147,13 @@ "more": "了解更多", "star_ask": "如果它帮你省了时间,请给仓库点个星标,让更多人找得到。", "cite": "如何引用:见 [CITATION.cff](CITATION.cff)。", - "stamp": "数据取自 {ts}(UTC)。每天两次:北京时间 0 点(UTC+8)、洛杉矶时间 0 点(America/Los_Angeles)。", + "stamp": "数据取自 {ts}(UTC)。每天更新。", "footer": "数据怎么算和如何参与:[{how}]({how})。新仓库规则:[docs/NEW_ARRIVALS.md](docs/NEW_ARRIVALS.md)。分类说明:[docs/CATEGORIES.md](docs/CATEGORIES.md)。提交改动:[CONTRIBUTING.md](CONTRIBUTING.md)。", "doc_title": "GitHub 飙升榜是怎么做的", "doc_back": "← 返回榜单", "intro": "多数榜单按总星标数排序,老项目长期占据榜首,正在爆发的新项目反而被埋没。GitHub 飙升榜按“每天新增多少星标”来排:统计近 7 天、30 天和 365 天的日均涨星,并按分类展示,AI 是默认置顶的主榜。每个分类先看“7 天新晋”榜(最近 7 天内创建的仓库),再看周、月、年涨星榜,总星标榜放在最后。数据全部来自 GitHub API。分类规则见 [docs/CATEGORIES.md](docs/CATEGORIES.md)。", "cat_title": "分类", - "schedule": "每天更新两次:北京时间 0 点(UTC+8)和美西洛杉矶时间 0 点(America/Los_Angeles,随夏令时变化)。每次更新都会按各自的时段单独存档。", + "schedule": "每天更新。每次更新都会存档。", "tops_title": "本期各类榜首", "tops_intro": "每个分类里每张榜的第 1 名,由脚本根据下方的真实数据自动生成。“无”表示本次运行该榜没有条目。", "tops_h_cat": "分类", @@ -222,7 +222,7 @@ "history": "下方表格為最新榜單。每次更新還會依時段另存一份:[榜單歷史]({index}) · [{latest}]({latest})。", "shown": "README 裡每張榜只列前 {m} 名;每張榜完整的前 {n} 名(來自 {total} 個已收錄專案)在 latest/ 檔案裡。", "title": "GitHub 漲星榜 / GitHub Risers", - "tagline": "依每天新增星號(而不是總星號)替 GitHub 儲存庫排名:AI 在前,接著是開發者工具、資安、Web / 前端、基礎建設、設計 / 創意與資料;每天更新兩次。", + "tagline": "依每天新增星號(而不是總星號)替 GitHub 儲存庫排名:AI 在前,接著是開發者工具、資安、Web / 前端、基礎建設、設計 / 創意與資料;每天更新。", "website": "網站", "links": "[{website}]({site}) · [運作方式]({how}) · [事實速查](docs/FACTS.md) · [歷史榜單]({index}) · [最新快照]({latest})", "top_note": "這裡每張榜只列前 10 名,完整的前 20 名見 [{latest}]({latest})。", @@ -241,7 +241,7 @@ "q2": "和 GitHub Trending 有什麼不同?", "a2": "GitHub Trending 是 GitHub 官方頁面;GitHub 漲星榜是獨立的開源專案,規則公開,有五種語言,並保存每次更新的歷史。它與 GitHub 官方沒有關聯。", "q3": "多久更新一次?", - "a3": "每天兩次,北京時間 0 點和洛杉磯時間 0 點;每次執行都會存檔。", + "a3": "每天更新;每次執行都會存檔。", "q4": "資料怎麼算?", "a4": "取最近 7、30 或 365 個完整 UTC 日的新增星號,再除以天數。數字全部來自 GitHub API,沒有手填或估算。", "q5": "怎麼看、怎麼參與?", @@ -249,13 +249,13 @@ "more": "了解更多", "star_ask": "如果它幫你省了時間,請給儲存庫按個星號,讓更多人找得到。", "cite": "如何引用:見 [CITATION.cff](CITATION.cff)。", - "stamp": "資料取自 {ts}(UTC)。每天兩次:北京時間 0 點(UTC+8)、洛杉磯時間 0 點(America/Los_Angeles)。", + "stamp": "資料取自 {ts}(UTC)。每天更新。", "footer": "資料怎麼算與如何參與:[{how}]({how})。新儲存庫規則:[docs/NEW_ARRIVALS.md](docs/NEW_ARRIVALS.md)。分類說明:[docs/CATEGORIES.md](docs/CATEGORIES.md)。提交改動:[CONTRIBUTING.md](CONTRIBUTING.md)。", "doc_title": "GitHub 飆升榜是怎麼做的", "doc_back": "← 回到榜單", "intro": "多數榜單依總星號數排序,老專案長期霸占榜首,正在爆發的新專案反而被埋沒。GitHub 飆升榜依「每天新增多少星號」來排:統計近 7 天、30 天和 365 天的日均漲星,並依分類呈現,AI 是預設置頂的主榜。每個分類先看「7 天新晉」榜(最近 7 天內建立的儲存庫),再看週、月、年漲星榜,總星號榜放在最後。資料全部來自 GitHub API。分類規則請見 [docs/CATEGORIES.md](docs/CATEGORIES.md)。", "cat_title": "分類", - "schedule": "每天更新兩次:北京時間 0 點(UTC+8)和美西洛杉磯時間 0 點(America/Los_Angeles,隨夏令時間變動)。每次更新都會依各自的時段單獨存檔。", + "schedule": "每天更新。每次更新都會存檔。", "tops_title": "本期各類榜首", "tops_intro": "每個分類裡每張榜的第 1 名,由腳本根據下方的真實資料自動產生。「無」表示本次執行該榜沒有項目。", "tops_h_cat": "分類", @@ -324,7 +324,7 @@ "history": "以下が最新のランキングです。更新のたびに時間帯ごとにも保存しています:[ランキング履歴]({index}) · [{latest}]({latest})。", "shown": "README では各ランキングの上位 {m} 件を表示します。各ランキングの上位 {n} 件の全体(収録 {total} プロジェクトから)は latest/ のファイルにあります。", "title": "GitHub スター急上昇ランキング / GitHub Risers", - "tagline": "総スター数ではなく、1 日あたりに増えたスター数で GitHub リポジトリを並べます。AI が先頭で、開発者ツール、セキュリティ、Web / フロントエンド、インフラ、デザイン / クリエイティブ、データが続きます。1 日 2 回更新。", + "tagline": "総スター数ではなく、1 日あたりに増えたスター数で GitHub リポジトリを並べます。AI が先頭で、開発者ツール、セキュリティ、Web / フロントエンド、インフラ、デザイン / クリエイティブ、データが続きます。毎日更新。", "website": "ウェブサイト", "links": "[{website}]({site}) · [仕組み]({how}) · [ファクト](docs/FACTS.md) · [履歴]({index}) · [最新]({latest})", "top_note": "ここでは各ランキングの上位 10 件だけを載せています。上位 20 件の全体は [{latest}]({latest}) にあります。", @@ -343,7 +343,7 @@ "q2": "GitHub Trending とどう違う?", "a2": "GitHub Trending は GitHub 公式のページです。GitHub Risers は独立したオープンソースのプロジェクトで、ルールを公開し、5 言語に対応し、更新のたびに履歴を残します。GitHub とは無関係です。", "q3": "更新は何回?", - "a3": "1 日 2 回、北京時間 0 時とロサンゼルス時間 0 時です。実行のたびに保存します。", + "a3": "毎日更新します。実行のたびに保存します。", "q4": "数値はどう出す?", "a4": "直近 7・30・365 の完了した UTC 日の増加スターを日数で割ります。数値はすべて GitHub API から取得し、手入力や推定はしません。", "q5": "使い方と参加方法は?", @@ -351,13 +351,13 @@ "more": "詳しく見る", "star_ask": "役に立ったら、ほかの人にも見つけてもらえるようリポジトリにスターを付けてください。", "cite": "引用の仕方:[CITATION.cff](CITATION.cff) を見てください。", - "stamp": "データ取得 {ts}(UTC)。1 日 2 回:北京時間 0 時(UTC+8)、ロサンゼルス時間 0 時(America/Los_Angeles)。", + "stamp": "データ取得 {ts}(UTC)。毎日更新。", "footer": "数字の出し方と参加方法:[{how}]({how})。新規リポジトリのルール:[docs/NEW_ARRIVALS.md](docs/NEW_ARRIVALS.md)。カテゴリ:[docs/CATEGORIES.md](docs/CATEGORIES.md)。プルリクエスト:[CONTRIBUTING.md](CONTRIBUTING.md)。", "doc_title": "GitHub Risers の仕組み", "doc_back": "← ランキングに戻る", "intro": "総スター数順のリストでは古い大物が居座り、いま伸びているものが埋もれます。GitHub Risers は 1 日あたりの増加スター数(直近 7・30・365 日)で並べ、カテゴリごとに表示します。AI がメインのランキングです。各カテゴリは、直近 7 日以内に作られたリポジトリの「新着」ランキング、週・月・年の増加ランキング、最後に総スター数の順です。数値はすべて GitHub API から取得しています。分類ルール:[docs/CATEGORIES.md](docs/CATEGORIES.md)。", "cat_title": "カテゴリ", - "schedule": "1 日 2 回更新:北京時間 0 時(UTC+8)とロサンゼルス時間 0 時(America/Los_Angeles、夏時間に追従)。更新ごとに時間帯別に保存されます。", + "schedule": "毎日更新。更新ごとに保存されます。", "tops_title": "今回のカテゴリ別 1 位", "tops_intro": "各カテゴリの各ランキングの 1 位を、下の実データからスクリプトで自動生成しています。「なし」は今回その枠に該当がないことを示します。", "tops_h_cat": "カテゴリ", @@ -422,7 +422,7 @@ "history": "아래가 최신 순위입니다. 업데이트할 때마다 시간대별로도 저장합니다: [순위 기록]({index}) · [{latest}]({latest}).", "shown": "README에는 랭킹마다 상위 {m}개를 싣습니다. 각 랭킹의 상위 {n}개 전체({total}개 수록 프로젝트 기준)는 latest/ 파일에 있습니다.", "title": "GitHub 스타 급상승 랭킹 / GitHub Risers", - "tagline": "총 스타 수가 아니라 하루에 늘어난 스타 수로 GitHub 저장소를 줄 세웁니다. AI가 맨 앞이고 개발자 도구, 보안, Web / 프론트엔드, 인프라, 디자인 / 크리에이티브, 데이터가 뒤따릅니다. 하루 두 번 갱신합니다.", + "tagline": "총 스타 수가 아니라 하루에 늘어난 스타 수로 GitHub 저장소를 줄 세웁니다. AI가 맨 앞이고 개발자 도구, 보안, Web / 프론트엔드, 인프라, 디자인 / 크리에이티브, 데이터가 뒤따릅니다. 매일 갱신합니다.", "website": "웹사이트", "links": "[{website}]({site}) · [작동 방식]({how}) · [팩트](docs/FACTS.md) · [지난 랭킹]({index}) · [최신]({latest})", "top_note": "여기에는 랭킹마다 상위 10개만 싣습니다. 상위 20개 전체는 [{latest}]({latest})에 있습니다.", @@ -441,7 +441,7 @@ "q2": "GitHub Trending과 뭐가 다른가요?", "a2": "GitHub Trending은 GitHub 공식 페이지입니다. GitHub Risers는 독립적인 오픈 소스 프로젝트로, 규칙을 공개하고 5개 언어를 지원하며 갱신 때마다 기록을 남깁니다. GitHub와는 관련이 없습니다.", "q3": "얼마나 자주 갱신하나요?", - "a3": "하루 두 번, 베이징 시간 0시와 로스앤젤레스 시간 0시입니다. 실행할 때마다 보관합니다.", + "a3": "매일 갱신합니다. 실행할 때마다 보관합니다.", "q4": "숫자는 어떻게 계산하나요?", "a4": "최근 7·30·365개의 완료된 UTC 일에 늘어난 스타를 그 일수로 나눕니다. 숫자는 모두 GitHub API에서 가져오며 손으로 넣거나 추정하지 않습니다.", "q5": "어떻게 보고 어떻게 기여하나요?", @@ -449,13 +449,13 @@ "more": "더 보기", "star_ask": "도움이 됐다면 다른 사람도 찾을 수 있도록 저장소에 스타를 눌러 주세요.", "cite": "인용 방법: [CITATION.cff](CITATION.cff)를 보세요.", - "stamp": "데이터 시각 {ts}(UTC). 하루 두 번: 베이징 0시(UTC+8), 로스앤젤레스 0시(America/Los_Angeles).", + "stamp": "데이터 시각 {ts}(UTC). 매일 갱신합니다.", "footer": "숫자를 만드는 방법과 참여 방법: [{how}]({how}). 신규 저장소 규칙: [docs/NEW_ARRIVALS.md](docs/NEW_ARRIVALS.md). 카테고리: [docs/CATEGORIES.md](docs/CATEGORIES.md). 풀 리퀘스트: [CONTRIBUTING.md](CONTRIBUTING.md).", "doc_title": "GitHub Risers 작동 방식", "doc_back": "← 랭킹으로 돌아가기", "intro": "총 스타 순 목록에서는 오래된 대형 프로젝트가 계속 상위를 차지하고, 지금 뜨는 프로젝트는 묻힙니다. GitHub Risers는 하루 평균 늘어난 스타 수(최근 7·30·365일)로 순위를 매기고 카테고리별로 보여 주며, AI가 메인 랭킹입니다. 각 카테고리는 최근 7일 안에 만들어진 저장소의 신규 랭킹, 주·월·연 증가 랭킹, 마지막으로 총 스타 순입니다. 모든 수치는 GitHub API에서 가져옵니다. 분류 규칙: [docs/CATEGORIES.md](docs/CATEGORIES.md).", "cat_title": "카테고리", - "schedule": "하루 두 번 갱신: 베이징 시간 0시(UTC+8)와 로스앤젤레스 시간 0시(America/Los_Angeles, 서머타임 반영). 갱신할 때마다 시간대별로 따로 보관합니다.", + "schedule": "매일 갱신합니다. 갱신할 때마다 보관합니다.", "tops_title": "이번 회차 카테고리별 1위", "tops_intro": "각 카테고리의 모든 랭킹 1위를 아래 실제 데이터로 스크립트가 자동 생성합니다. '없음'은 이번 실행에서 해당 랭킹에 항목이 없다는 뜻입니다.", "tops_h_cat": "카테고리", diff --git a/tools/local_daily.sh b/tools/local_daily.sh index ec8b082..4a26ab0 100755 --- a/tools/local_daily.sh +++ b/tools/local_daily.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # Local daily update: fetch real star data, regenerate READMEs, verify, commit and push. -# Slots (match the Actions schedule): --slot beijing runs only at 00:xx Asia/Shanghai, -# --slot la runs only at 00:xx America/Los_Angeles (DST-aware), no --slot runs always. +# Production cadence is once a day at America/Los_Angeles midnight (same as Actions). +# --slot la runs only at 00:xx America/Los_Angeles (DST-aware); --slot beijing is optional +# (00:xx Asia/Shanghai, writes cn-0000); no --slot runs always. # Each run is also archived under rankings/// and copied to latest/. # Every run also re-discovers per-category 7-day new-repo rankings via the search API (several minutes). # Use this when GitHub Actions cannot run (billing, quota, outage). Reads GITHUB_TOKEN; diff --git a/tools/mac_i18n_cron.sh b/tools/mac_i18n_cron.sh index c1c9d2e..c8d0c97 100755 --- a/tools/mac_i18n_cron.sh +++ b/tools/mac_i18n_cron.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # Owner's-Mac job: translate the new-repo descriptions (and write their one-line hooks) that the GitHub Actions # daily-update could not, because translation needs a signed-in agent CLI and the repo has no CURSOR_API_KEY secret. -# Runs from launchd ~1h after each Actions slot (Beijing 01:15 and 17:15, see tools/com.jammyfu.risers-i18n.plist). +# Runs from launchd once a day ~1–2h after the Los Angeles midnight Actions slot +# (Beijing 17:15; see tools/com.jammyfu.risers-i18n.plist). # # tools/mac_i18n_cron.sh # pull, translate if anything is missing, render, verify, commit, push to main # tools/mac_i18n_cron.sh --no-push # same, but commit locally only