First off — thank you for taking the time to contribute! 🎉
Tip
TL;DR Quick Start (3 steps):
- Find an issue tagged
good first issueand comment/assign - Setup your local environment:
git clone→make setup→make test(all green) - Ship your change on a branch, then open a PR with
Fixes #<issue-number>in the description
Full details in the sections below.
New Grad Jobs is a fully automated job aggregator that helps new graduates find their first tech role. Every contribution — whether it's submitting a missing job, fixing a bug in the scraper, improving the frontend, or helping with docs — directly helps thousands of job seekers.
Heads-up:
README.mdis auto-generated every 5 minutes by GitHub Actions. Never edit it manually — your changes will be overwritten.
- Ways to Contribute
- The AI-Assisted Workflow
- Local Development Setup
- Full Contribution Lifecycle
- Project Architecture
- Branching & Commit Standards
- Pull Request Checklist
- Reporting Issues
- Adding a Company or Job
- Code Style
- Good First Issues
- When Will My PR Be Merged?
We organize work into clear tiers. If you are new here, look for issues tagged appropriately to get started:
good first issue— Great for your first PR. Small, scoped, and well-defined tasks (e.g., adding a missing company endpoint).help wanted— Medium complexity tasks where maintainer support is available.architecture proposal— For proposing major structural changes. Discuss these before writing code!
Non-coding contributions:
- 🐛 Bug report or 🆕 Missing job: Use our Issue Templates.
- 🌐 Translation: Add a translated README (e.g.
README.zh-CN.md).
We are a solo-maintained repository, which means we rely heavily on AI to help manage contributions. We use CodeRabbit as our Lead Architect.
To write code for this repository, follow this exact workflow:
- Open an Issue: Use the AI-Assisted Task template or add the
plan-melabel to a Bug/Feature issue. - Wait 10 Minutes: CodeRabbit will automatically scan our entire codebase and reply to your issue with a step-by-step Implementation Plan.
- Refine the Plan: If the plan looks wrong or you have questions, reply to the issue with
@coderabbitai clarify <your question>. CodeRabbit will update the plan. - Write the Code: You can hand CodeRabbit's exact plan to Cursor, GitHub Copilot, or write it yourself.
- Open a PR: You must link the PR to the issue (
Fixes #123). - Auto-Title your PR: Make the title of your PR literally just
@coderabbitai. CodeRabbit will read your code and automatically rename the PR to a perfect Conventional Commit title (e.g.,feat: add workday scraper). - The Gatekeeper: CodeRabbit will cross-examine your code against its original plan. If you skipped tests or cut corners, it will block your PR before a human ever looks at it.
We believe in a "single command setup". You do not need to decipher a 10-page guide to run this project.
- Python 3.11+
- Git
make
# 1. Fork the repo on GitHub, then clone YOUR fork
git clone https://github.com/<your-username>/New-Grad-Jobs.git
cd New-Grad-Jobs
# 2. Run the automated setup
make setupThe make setup command automatically:
- Creates a Python virtual environment (
.venv). - Installs runtime dependencies from
requirements.txtand dev/test tooling frompyproject.toml(.[dev], includingpytestandpytest-cov). - Configures
pre-commithooks to run on every commit.
(Alternatively, if you use DevContainers, just open this repository in VS Code and click "Reopen in Container". Everything is pre-configured!)
# Activate the environment
source .venv/bin/activate
# Run the tests to ensure everything is green
make test
# To run the scraper locally (takes 4-6 minutes)
make run
# To generate forecast artifacts from existing docs/market-history.json
# (requires GOOGLE_API_KEY)
make predict| File | Role | Edit? |
|---|---|---|
config.yml |
Central configuration — companies, filters, search terms | ✅ Yes |
scripts/update_jobs.py |
Core scraper + filterer + artifact generator | ✅ Yes |
.github/workflows/update-jobs.yml |
GitHub Actions job | ✅ Yes (test via manual trigger) |
requirements.txt |
Python dependencies | ✅ Yes |
docs/ |
GitHub Pages website (HTML/CSS/JS) | ✅ Yes |
README.md |
AUTO-GENERATED — never edit manually | ❌ Never |
docs/predictions-artifacts.md |
Forecast artifact contract for local + deployed /docs/ |
✅ Yes |
This section documents the end-to-end lifecycle of a contribution, from claiming an issue to getting your PR merged with a clean history. Follow every phase in order.
Before writing a single line of code, you must claim and fully understand the issue.
Comment on the issue with the bot command:
/assign
This assigns the issue to you and signals to other contributors that it is being worked on. Do not start work until you are assigned — it prevents duplicate effort.
Read the issue description carefully. If anything is ambiguous — especially for architecture or help wanted issues — ask clarifying questions in the issue thread before writing code.
Good questions to ask:
- Is the expected behavior clearly defined?
- Are there specific files or functions I should or should not touch?
- Are there existing tests I must not break?
Rule: The cost of a clarifying question is 5 minutes. The cost of building the wrong thing is a reverted PR.
This phase ensures your local environment is correctly isolated from the upstream repository.
If you haven't already, fork the repo on GitHub, then clone your fork (not the upstream):
git clone https://github.com/<your-username>/New-Grad-Jobs.git
cd New-Grad-Jobsgit remote add upstream https://github.com/ambicuity/New-Grad-Jobs.gitVerify the remotes are set up correctly:
git remote -v
# Expected output:
# origin https://github.com/<your-username>/New-Grad-Jobs.git (fetch)
# origin https://github.com/<your-username>/New-Grad-Jobs.git (push)
# upstream https://github.com/ambicuity/New-Grad-Jobs.git (fetch)
# upstream https://github.com/ambicuity/New-Grad-Jobs.git (push)You will never push to upstream. upstream is read-only — it is only used to pull the latest changes from the maintainer's repository.
Never work on main. Create a short-lived, descriptively named branch:
git checkout -b feat/add-workday-microsoft
# or
git checkout -b fix/issue-123-greenhouse-timeout
# or
git checkout -b docs/update-contributing-guideBranch naming convention:
| Prefix | When to use |
|---|---|
feat/ |
New feature or new company scraper |
fix/ |
Bug fix |
docs/ |
Documentation only |
chore/ |
Maintenance (deps, CI config, etc.) |
Before writing code, confirm the test suite is green on your machine:
source .venv/bin/activate
make testIf tests fail before your changes, stop and report it — do not proceed on a broken base.
Stay strictly within the scope of the issue. Do not:
- Rename unrelated variables or refactor functions you didn't touch.
- Fix style issues in lines you didn't author.
- Add unrelated features "while you're in there".
Code review scope creep is one of the most common reasons PRs are rejected.
If your change adds or modifies any function in scripts/update_jobs.py, add a corresponding test in tests/:
# Run only your new tests to confirm they pass
pytest tests/test_your_module.py -v
# Run the full suite to confirm you haven't broken anything
make testTests must be deterministic — inject datetime via parameters. No live network calls in tests.
Our pre-commit suite runs whitespace checks, YAML/JSON validation, import sorting, and secret detection. Run it manually before committing:
pre-commit run --all-filesFix any errors it reports before proceeding.
# Syntax check (required — must produce zero output)
python -m py_compile scripts/update_jobs.py
# Style check (optional but recommended)
python -m flake8 scripts/ --max-line-length=120This is the most commonly skipped phase — and the one that causes the most pain at review time.
Before pushing your branch, always sync with the upstream main:
# Step 1: Fetch all changes from the upstream repository
git fetch upstream
# Step 2: Rebase your feature branch on top of upstream/main
git rebase upstream/mainWhy rebase instead of merge? Rebasing places your commits on top of the latest upstream commits, producing a clean, linear history with no merge commits. Our PRs are squash-merged, so a clean rebase prevents conflicts at merge time.
If you encounter rebase conflicts:
# After resolving conflicts in the affected files:
git add <resolved-files>
git rebase --continue
# To abort the rebase and return to your previous state:
git rebase --abortEach commit should represent one logical unit of work. Avoid:
"fix"(too vague)"WIP"/"temp"commits- Mixing unrelated changes in a single commit
Good commit examples:
feat(config): add Microsoft to Workday scraper targets
fix(greenhouse): handle paginated response beyond 100 jobs
docs(contributing): add upstream remote setup instructions
test(filter): add edge case for None location field
We enforce Conventional Commits:
<type>(<scope>): <short summary in imperative mood>
Types:
feat – new feature or new company added to scraper
fix – bug fix
docs – documentation only
test – adding missing tests
chore – maintenance (deps, CI config, etc.)
refactor – code change that is neither a fix nor a feature
git push origin feat/add-workday-microsoftOpen the PR against main in the upstream repository (ambicuity/New-Grad-Jobs). Do not target your own fork's main.
GitHub will default to the correct base if your fork is set up properly.
Fill out the PR description template completely. Partial descriptions slow down review.
For scraper changes, include:
- Which API/source was modified
- Sample output showing jobs found (or a zero-output explanation)
- Whether any timeouts or errors were seen in local testing
Include one of these in the PR description to automatically close the issue when the PR is merged:
Fixes #123
Closes #456
Resolves #789
This is a hard requirement. Unlinked PRs will be asked to add the link before review begins.
Important
The bot will reject your PR automatically if the title is wrong. It will post a comment explaining the correct format. Fix the title and the check will re-run.
<type>(<scope>): <short summary in imperative mood, lowercase, no period>
| Type | Use when… | Example |
|---|---|---|
feat |
You added a new feature or new company | feat(config): add Stripe to Greenhouse companies |
fix |
You fixed a bug | fix(scraper): handle None date in normalize_date_string |
docs |
Documentation only, no code change | docs(contributing): clarify assignment workflow |
test |
Added or fixed tests, no production code change | test(filter): add edge case for empty location string |
chore |
Maintenance — deps, CI config, housekeeping | chore(ci): bump actions/checkout from v3 to v4 |
refactor |
Code change that is neither a fix nor a feature | refactor(scraper): extract date parsing into helper |
perf |
Performance improvement | perf(scraper): cache company tier lookups with lru_cache |
Use the area of the repo your change touches:
| Scope | When to use |
|---|---|
scraper |
Changes to scripts/update_jobs.py |
config |
Changes to config.yml |
filter |
Changes to filter_jobs() or related logic |
dedup |
Changes to deduplicate_jobs() or get_job_key() |
ci |
GitHub Actions workflow changes |
docs |
Markdown files, docs/ website |
tests |
Files under tests/ |
frontend |
HTML/CSS/JS in docs/ |
✅ feat(config): add Palantir and Two Sigma to Greenhouse companies
✅ fix(scraper): skip jobs with NaN posted_at instead of crashing
✅ fix(filter): handle Unicode characters in company names
✅ docs(readme): update contribution steps in CONTRIBUTING.md
✅ test(dedup): add test for get_job_key with math.nan input
✅ chore(ci): pin trivy-action to v0.28.0 for reproducibility
✅ refactor(scraper): move sponsorship detection to detect_sponsorship_flags()
✅ perf(scraper): reduce max_workers from 300 to 100 for Workday endpoints
❌ Update config ← no type, no scope
❌ Fixed the bug ← no type, too vague
❌ feat: Added new companies to the list ← not imperative mood, has capital letter
❌ WIP: testing stuff ← not a valid type
Name your PR exactly @coderabbitai as the title. CodeRabbit will read your
diff and rename it to a correct Conventional Commit title automatically within
a few seconds of opening the PR.
When a maintainer or CodeRabbit requests changes:
- Respond to every comment, even if just to acknowledge.
- If you disagree with a suggestion, explain your reasoning clearly. We prefer a short discussion over a silent revert.
- Do not close the PR and open a new one to avoid review comments.
After rounds of review, your branch will likely have accumulated noisy commits like "address review feedback", "fix typo in test", "forgot to add import". Before your PR is merged, clean these up:
# Squash all commits back to a clean set of logical commits
git rebase -i upstream/mainIn the interactive editor, pick your first meaningful commit and squash or fixup the rest:
pick a1b2c3d feat(config): add Microsoft to Workday targets
fixup d4e5f6g fix: address review feedback
fixup 7h8i9j0 fix: forgot to add import
The result should be one (or a small number of) clean, atomic commits with proper Conventional Commit messages.
After rebasing or squashing, you must force-push your branch. Always use --force-with-lease — it is a safety net that will refuse to overwrite commits someone else may have pushed to your branch:
git push origin feat/add-workday-microsoft --force-with-leaseNever use
git push --forcewithout--lease. Bare--forcecan destroy commits without warning.
The PR will automatically update with the cleaned history. No need to close and reopen it.
New-Grad-Jobs/
├── scripts/
│ └── update_jobs.py # Main scraper (~2000 lines)
│ ├── create_optimized_session() # Connection pooling
│ ├── fetch_greenhouse_jobs() # Greenhouse API
│ ├── fetch_lever_jobs() # Lever API
│ ├── fetch_google_jobs() # Google Careers API
│ ├── fetch_jobspy_jobs() # JobSpy (Indeed/LinkedIn)
│ ├── filter_jobs() # Eligibility filter
│ ├── categorize_job() # Role categorization
│ └── generate_readme() # README table output
├── config.yml # Companies, filters, search configuration
├── jobs.json # Full jobs dataset (auto-generated)
├── docs/ # GitHub Pages frontend
│ ├── index.html # Entry point (loads terminal/*.jsx)
│ ├── terminal/ # React UI (in-browser Babel, no build step)
│ │ ├── app.jsx # Root component / top bar
│ │ ├── dashboard.jsx # Jobs board view
│ │ ├── contributors.jsx # Contributors view
│ │ └── data.jsx # jobs.json loading + shaping
│ └── jobs.json # Mirrored jobs data for Pages
└── .github/
├── workflows/
│ └── update-jobs.yml # Runs every 5 minutes
└── ISSUE_TEMPLATE/ # Structured issue forms
Data flow: config.yml → Multi-source parallel fetch → Filter/deduplicate → Categorize → jobs.json + README.md
We use GitHub Flow (Trunk-based development). This means:
mainis always deployable and green.- All feature work happens in short-lived branches created from
main.
When your PR is merged, it will be "Squash and Merged" to maintain a single atomic commit per feature. This is why clean, rebased history matters — the single squash commit is what lands in main.
Before opening a PR, confirm every item below:
- I have commented
/assignon the issue and have been officially assigned. - I have added
upstreamas a remote (git remote add upstream https://github.com/ambicuity/New-Grad-Jobs.git). - I am working on a feature branch — not on
main. - I ran
git fetch upstream && git rebase upstream/mainbefore pushing. -
python -m py_compile scripts/update_jobs.pypasses with no errors. -
make testpasses with no failures. -
pre-commit run --all-filespasses with no errors. - I have reverted
README.mdif it was modified during testing:git checkout README.md. - I have verified any scraper changes locally (
cd scripts && python update_jobs.py). - My changes are scoped to the described problem — no unrelated modifications.
- Commit messages follow the Conventional Commits format.
- The PR is linked to the issue with
Fixes #<number>in the description. - The PR is opened against
mainin the upstream repository, not my fork. - I have updated relevant documentation (
config.ymlcomments,README.md, etc.) if applicable.
Use the structured issue templates:
- 🤖 AI-Assisted Task — Start here! CodeRabbit will analyze the repo and generate a step-by-step coding plan for your idea.
- Bug Report — broken links, scraper failures, incorrect listings
- Feature Request — suggest an improvement
- New Role — submit a job our scraper missed
- Edit Role — report a closed or incorrect listing
- Architecture Proposal — major structural changes
💬 Not sure if it's an issue? Start a Discussion instead.
🔒 Security issues: Please email
contact@riteshrana.engineer— do not open a public issue. See SECURITY.md.
The scraper is configured via config.yml. To add a new company:
- Identify the company's ATS (Applicant Tracking System): Greenhouse, Lever, or Workday
- Find the API endpoint:
- Greenhouse:
https://api.greenhouse.io/v1/boards/<slug>/jobs - Lever:
https://api.lever.co/v0/postings/<slug>
- Greenhouse:
- Add the entry to the appropriate section in
config.yml - Test locally with
cd scripts && python update_jobs.py - Submit a PR with the addition
Use the New Role issue template.
- Python: Follow PEP 8. Use type hints for all new functions.
- JavaScript / JSX (
docs/terminal/*.jsx): React 18 transpiled in-browser via Babel Standalone — no build step. Follow existing patterns. - YAML (
config.yml): Use 2-space indentation. - Markdown: Use ATX-style headers (
#,##), not underline style.
Run a quick style check before committing:
python -m py_compile scripts/update_jobs.py # Syntax
python -m flake8 scripts/ --max-line-length=120 --ignore=E501 # Style (optional)New here? Look for issues tagged:
good first issue— small, self-contained taskshelp wanted— medium tasks where maintainer support is availabledocumentation— docs improvements (no coding required)
Internationalization (i18n): Want to translate the README? Add README.<lang>.md (e.g., README.zh-CN.md) and open a PR. No coding skills required — just fluency in the target language!
The maintainer (@ambicuity) merges PRs manually after reviewing. Here is the exact decision matrix:
| Check | Required? |
|---|---|
| All required CI checks are green | ✅ Yes |
At least one maintainer approval (@ambicuity) |
✅ Yes |
PR body contains Fixes #N / Closes #N |
✅ Yes |
| PR title follows Conventional Commits format | ✅ Yes |
No merge conflicts with main |
✅ Yes |
| PR has been open ≥ 24 hours (except trivial config additions) | ✅ Yes |
| Failing Check | Can We Merge? | Why |
|---|---|---|
| Codecov upload | ✅ Yes | Informational only — token issues don't block correctness |
| Failing Check | Why It Blocks |
|---|---|
CI — Lint & Validate (lint-and-validate job) |
Syntax errors or broken config will break the scraper |
CI — Lint & Validate (test job) |
Failing pytest run — known regression |
Code Hygiene (Pre-commit) |
Secrets, malformed YAML, broken imports |
CodeQL Security Scan |
Security vulnerability in merged code |
| Merge conflict | Cannot squash-merge a conflicted PR |
Every contribution is recognized — see the Contributors Hall of Fame.
- Comment on your PR or the related issue
- A maintainer will review and add you manually if needed
Thank you for helping new graduates land their first job. Every contribution matters. 🚀