Repository navigation
Contributing
Want to fix a bug, tighten a check or make the site nicer? Good. The rules for contributing live in CONTRIBUTING.md, and this page doesn't repeat them. What it adds is the map: which bot does what, and where your change fits.
Just want a repo listed? You don't need any of this. Use the Submit a repo form.
-
Fork, branch from
main, make your change with tests, and open a pull request. -
Run the checks before you push:
python3 -m unittest discover -s scripts -t . npm --prefix site test npx prettier --check .
-
Use Conventional Commits for the PR title, such as
fix(submit): reject repos with no README, in 72 characters or fewer. PRs are squash-merged, so the title becomes the commit. -
Every PR needs an approving review and passing checks.
-
Planning something big? Start a thread in Ideas first.
-
Never edit
data/entries.jsonby hand in a PR. The bot owns it.
Everything you contribute is released under CC0.
| Path | What it holds |
|---|---|
data/entries.json |
Every entry: repo, category, description, status. The single source of truth. |
data/schema.json |
The JSON Schema that entries.json must pass. |
data/categories.json |
The 13 categories. |
data/osi-licenses.json |
The OSI-approved license IDs the license check accepts. |
scripts/ |
The bots, in Python 3.12 with the standard library only. Tests are in scripts/tests/. |
scripts/devstream/ |
Shared modules: trust, quality, license_detect, description, issue_form, likes and the GitHub client. |
scripts/blocklist.txt |
The spam wording the description filter rejects. |
site/ |
The Astro site, with a little vanilla JavaScript for sorting and filtering. |
fuzz/ |
Fuzz targets for the parsers. |
scheduler/ |
The Cloudflare Worker that starts the timed workflows. |
There's also a stats branch that only the bot writes to. It holds the Like counts (likes.json), the entry-to-Discussion map (discussions.json), daily snapshots, burst flags, health-check state and the trust cache. Keeping all that off main means the hourly Likes sync never touches the protected branch.
Every bot is a GitHub Actions workflow in .github/workflows/ that runs a script in scripts/. They write as a GitHub App, devstreamhq-bot.
Submit form ─> submit.yml ─> bot PR ─> pr-checks.yml + auto-approve.yml ─> merge
(submit.yml then opens the entry's Discussion)
scheduler/ (every 15 min) ─> sweep.yml, likes.yml, on-merge.yml, rot.yml, stars.yml
likes.yml (hourly) ─> stats branch ─> deploy.yml ─> the site
on-merge.yml (hourly) ─> any missing Discussions
rot.yml (weekly) ─> bot PR for warnings, archives, removals
Report form ─> reports.yml ─> label, hide PR, or a one-repo rot.yml run
GitHub's own scheduler skips runs when it's busy, so a small Cloudflare Worker in scheduler/ starts the timed workflows. It runs every 15 minutes and picks what to start from the time.
| Workflow | Runs | What it does | Script |
|---|---|---|---|
submit.yml |
A submission issue is opened | Checks the account and the repo against the Quality Bar. On a pass, opens a PR adding the entry and turns on auto-merge. On a fail, comments the reasons and closes the issue. | scripts/submit.py |
pr-checks.yml |
Every PR to main
|
validate (entries against the schema), test (the unit tests), lint (Prettier) and site (the site's tests and build). |
scripts/validate_data.py |
auto-approve.yml |
A bot PR is opened | Approves the bot's own PRs, only when they change nothing but data/entries.json. |
|
on-merge.yml |
Hourly at :30, as a backstop | Opens a Discussion for any entry that doesn't have one yet and records it on the stats branch. Submit and the health check open threads themselves right after they merge. |
scripts/on_merge.py |
likes.yml |
Hourly at :15 | Counts trusted 👍 reactions, ranks entries, runs the burst and drip checks, writes the stats branch. |
scripts/sync_likes.py |
deploy.yml |
After merges, Likes syncs and site changes | Builds the site from main plus stats, and deploys it. |
|
rot.yml |
Mondays at 06:45 UTC, or one repo on demand | The health check. Opens a PR for any status change, then comments on the affected Discussions. | scripts/rot.py |
reports.yml |
A report issue is opened | Labels the report, queues a health check, or opens a PR hiding the entry. See Reporting an Entry. | scripts/reports.py |
sweep.yml |
Every 15 minutes, and after each submission or report run | Re-runs submissions and reports that no run finished, re-runs an overdue health check, restarts a Likes or Discussions run that hasn't started in 2 hours, closes the health check's abandoned PRs, and clears abandoned bot branches. | scripts/sweep.py |
stars.yml |
Daily | Opens one issue when the repo reaches 100 stars. | scripts/stars.py |
codeql.yml, scorecard.yml, cflite_pr.yml
|
PRs, pushes and a schedule | Code scanning, the OpenSSF Scorecard and fuzzing. | |
release.yml |
A v* tag is pushed |
Tests and builds the site, then publishes a GitHub release with the source and site tarballs. |
If you touch .github/workflows/, the review looks for these:
- Actions are pinned to a full commit SHA, with the version in a comment.
-
Permissions start at
{}and each job asks only for what it needs. -
Text from issues never goes into
${{ }}insiderun:. Pass it throughenv:or a file. Issue text is written by strangers. - Deciding and writing are separate jobs. The job that reads an issue and decides runs with a read-only token. Only a later job gets the App token.
- No
pull_request_targetcheckout of PR code.
The bot's public comments use fixed strings and the normalised owner/name, never text copied from the issue. Keep it that way, so nobody can make the bot say something or ping someone.
Issues labelled good first issue are small and well-scoped. The Roadmap shows what's planned for each milestone. Questions go to Discussions Q&A.
Found a security problem while you were in there? Don't open a PR or an issue. See Security.
Site · Repo · Discussions · CC0 license
Getting listed
Likes
Keeping it clean
Help
The project