MadsLorentzen/ai-job-searchPublic

The job search that runs on your machine. AI job application framework built on Claude Code: evaluate postings, tailor CVs, write cover letters, prep interviews. Fork it and own it.

AI summary: An AI-powered job application framework running locally via Claude Code to automate your job search.

Stars
44.9K
+245 today
Forks
15.5K
Watchers
164
Open issues
4
Open PRs
3
Contributors
~70
Commits
304
Branches
1

PythonMITCreated Mar 18, 2026Last push 1d agoLatest release v1.7.2+946 stars this week+4.3K this month

Quick answers

What is ai-job-search?
An AI-powered job application framework running locally via Claude Code to automate your job search.
What does ai-job-search do?
AI Job Search is a local workflow built on Claude Code designed to automate and enhance the job application process. It systematically scrapes job portals, evaluates open positions against a user's pre-configured profile, and generates highly tailored CVs and cover letters using LaTeX. The framework utilizes a multi-agent approach, employing drafter and reviewer agents to refine application materials before final output. It also includes comprehensive tools for tracking applications in Notion, monitoring Gmail for interview invites, generating offline HTML dashboards, and providing customized interview preparation based on the specific job requirements.
Who is ai-job-search for?
Job seekers, developers, and professionals looking for a systematic, privacy-first, and highly automated approach to managing their career search.
How do I get started with ai-job-search?
gh repo fork MadsLorentzen/ai-job-search --clone
How popular is ai-job-search on GitHub?
MadsLorentzen/ai-job-search has 44,877 stars and 15,515 forks on GitHub, and gained 946 stars in the last 7 days.
What license does ai-job-search use?
MadsLorentzen/ai-job-search is released under the MIT license.

Star history

since Jul 28, 2026
020K40KJul 2026Aug 2026Sep 2026Oct 2026
44.9K stars as of Oct 4, 2026. Measured daily since Jul 28, 2026; GitHub no longer exposes earlier star timestamps.

Contribution activity

commits per day, last 52 weeks
OctNovDecJanFebMarAprMayJunJulAugSepMonWedFri2025-10-05: 0 commits2025-10-06: 0 commits2025-10-07: 0 commits2025-10-08: 0 commits2025-10-09: 0 commits2025-10-10: 0 commits2025-10-11: 0 commits2025-10-12: 0 commits2025-10-13: 0 commits2025-10-14: 0 commits2025-10-15: 0 commits2025-10-16: 0 commits2025-10-17: 0 commits2025-10-18: 0 commits2025-10-19: 0 commits2025-10-20: 0 commits2025-10-21: 0 commits2025-10-22: 0 commits2025-10-23: 0 commits2025-10-24: 0 commits2025-10-25: 0 commits2025-10-26: 0 commits2025-10-27: 0 commits2025-10-28: 0 commits2025-10-29: 0 commits2025-10-30: 0 commits2025-10-31: 0 commits2025-11-01: 0 commits2025-11-02: 0 commits2025-11-03: 0 commits2025-11-04: 0 commits2025-11-05: 0 commits2025-11-06: 0 commits2025-11-07: 0 commits2025-11-08: 0 commits2025-11-09: 0 commits2025-11-10: 0 commits2025-11-11: 0 commits2025-11-12: 0 commits2025-11-13: 0 commits2025-11-14: 0 commits2025-11-15: 0 commits2025-11-16: 0 commits2025-11-17: 0 commits2025-11-18: 0 commits2025-11-19: 0 commits2025-11-20: 0 commits2025-11-21: 0 commits2025-11-22: 0 commits2025-11-23: 0 commits2025-11-24: 0 commits2025-11-25: 0 commits2025-11-26: 0 commits2025-11-27: 0 commits2025-11-28: 0 commits2025-11-29: 0 commits2025-11-30: 0 commits2025-12-01: 0 commits2025-12-02: 0 commits2025-12-03: 0 commits2025-12-04: 0 commits2025-12-05: 0 commits2025-12-06: 0 commits2025-12-07: 0 commits2025-12-08: 0 commits2025-12-09: 0 commits2025-12-10: 0 commits2025-12-11: 0 commits2025-12-12: 0 commits2025-12-13: 0 commits2025-12-14: 0 commits2025-12-15: 0 commits2025-12-16: 0 commits2025-12-17: 0 commits2025-12-18: 0 commits2025-12-19: 0 commits2025-12-20: 0 commits2025-12-21: 0 commits2025-12-22: 0 commits2025-12-23: 0 commits2025-12-24: 0 commits2025-12-25: 0 commits2025-12-26: 0 commits2025-12-27: 0 commits2025-12-28: 0 commits2025-12-29: 0 commits2025-12-30: 0 commits2025-12-31: 0 commits2026-01-01: 0 commits2026-01-02: 0 commits2026-01-03: 0 commits2026-01-04: 0 commits2026-01-05: 0 commits2026-01-06: 0 commits2026-01-07: 0 commits2026-01-08: 0 commits2026-01-09: 0 commits2026-01-10: 0 commits2026-01-11: 0 commits2026-01-12: 0 commits2026-01-13: 0 commits2026-01-14: 0 commits2026-01-15: 0 commits2026-01-16: 0 commits2026-01-17: 0 commits2026-01-18: 0 commits2026-01-19: 0 commits2026-01-20: 0 commits2026-01-21: 0 commits2026-01-22: 0 commits2026-01-23: 0 commits2026-01-24: 0 commits2026-01-25: 0 commits2026-01-26: 0 commits2026-01-27: 0 commits2026-01-28: 0 commits2026-01-29: 0 commits2026-01-30: 0 commits2026-01-31: 0 commits2026-02-01: 0 commits2026-02-02: 0 commits2026-02-03: 0 commits2026-02-04: 0 commits2026-02-05: 0 commits2026-02-06: 0 commits2026-02-07: 0 commits2026-02-08: 0 commits2026-02-09: 0 commits2026-02-10: 0 commits2026-02-11: 0 commits2026-02-12: 0 commits2026-02-13: 0 commits2026-02-14: 0 commits2026-02-15: 0 commits2026-02-16: 0 commits2026-02-17: 0 commits2026-02-18: 0 commits2026-02-19: 0 commits2026-02-20: 0 commits2026-02-21: 0 commits2026-02-22: 0 commits2026-02-23: 0 commits2026-02-24: 0 commits2026-02-25: 0 commits2026-02-26: 0 commits2026-02-27: 0 commits2026-02-28: 0 commits2026-03-01: 0 commits2026-03-02: 0 commits2026-03-03: 0 commits2026-03-04: 0 commits2026-03-05: 0 commits2026-03-06: 0 commits2026-03-07: 0 commits2026-03-08: 0 commits2026-03-09: 0 commits2026-03-10: 0 commits2026-03-11: 0 commits2026-03-12: 0 commits2026-03-13: 0 commits2026-03-14: 0 commits2026-03-15: 0 commits2026-03-16: 0 commits2026-03-17: 0 commits2026-03-18: 0 commits2026-03-19: 0 commits2026-03-20: 1 commit2026-03-21: 0 commits2026-03-22: 0 commits2026-03-23: 0 commits2026-03-24: 0 commits2026-03-25: 0 commits2026-03-26: 0 commits2026-03-27: 0 commits2026-03-28: 0 commits2026-03-29: 0 commits2026-03-30: 0 commits2026-03-31: 0 commits2026-04-01: 0 commits2026-04-02: 0 commits2026-04-03: 0 commits2026-04-04: 0 commits2026-04-05: 0 commits2026-04-06: 0 commits2026-04-07: 0 commits2026-04-08: 0 commits2026-04-09: 0 commits2026-04-10: 0 commits2026-04-11: 0 commits2026-04-12: 0 commits2026-04-13: 0 commits2026-04-14: 0 commits2026-04-15: 0 commits2026-04-16: 0 commits2026-04-17: 0 commits2026-04-18: 0 commits2026-04-19: 0 commits2026-04-20: 0 commits2026-04-21: 0 commits2026-04-22: 0 commits2026-04-23: 0 commits2026-04-24: 4 commits2026-04-25: 1 commit2026-04-26: 0 commits2026-04-27: 0 commits2026-04-28: 3 commits2026-04-29: 2 commits2026-04-30: 0 commits2026-05-01: 0 commits2026-05-02: 0 commits2026-05-03: 0 commits2026-05-04: 0 commits2026-05-05: 0 commits2026-05-06: 0 commits2026-05-07: 0 commits2026-05-08: 0 commits2026-05-09: 0 commits2026-05-10: 0 commits2026-05-11: 0 commits2026-05-12: 0 commits2026-05-13: 0 commits2026-05-14: 0 commits2026-05-15: 0 commits2026-05-16: 0 commits2026-05-17: 0 commits2026-05-18: 0 commits2026-05-19: 0 commits2026-05-20: 0 commits2026-05-21: 0 commits2026-05-22: 0 commits2026-05-23: 0 commits2026-05-24: 0 commits2026-05-25: 0 commits2026-05-26: 0 commits2026-05-27: 0 commits2026-05-28: 0 commits2026-05-29: 0 commits2026-05-30: 0 commits2026-05-31: 0 commits2026-06-01: 0 commits2026-06-02: 0 commits2026-06-03: 0 commits2026-06-04: 0 commits2026-06-05: 0 commits2026-06-06: 0 commits2026-06-07: 0 commits2026-06-08: 0 commits2026-06-09: 0 commits2026-06-10: 0 commits2026-06-11: 0 commits2026-06-12: 0 commits2026-06-13: 0 commits2026-06-14: 0 commits2026-06-15: 1 commit2026-06-16: 0 commits2026-06-17: 0 commits2026-06-18: 0 commits2026-06-19: 0 commits2026-06-20: 0 commits2026-06-21: 0 commits2026-06-22: 0 commits2026-06-23: 0 commits2026-06-24: 0 commits2026-06-25: 0 commits2026-06-26: 0 commits2026-06-27: 0 commits2026-06-28: 0 commits2026-06-29: 2 commits2026-06-30: 0 commits2026-07-01: 0 commits2026-07-02: 0 commits2026-07-03: 0 commits2026-07-04: 0 commits2026-07-05: 6 commits2026-07-06: 3 commits2026-07-07: 9 commits2026-07-08: 12 commits2026-07-09: 5 commits2026-07-10: 12 commits2026-07-11: 5 commits2026-07-12: 6 commits2026-07-13: 5 commits2026-07-14: 4 commits2026-07-15: 6 commits2026-07-16: 2 commits2026-07-17: 5 commits2026-07-18: 4 commits2026-07-19: 5 commits2026-07-20: 8 commits2026-07-21: 5 commits2026-07-22: 4 commits2026-07-23: 2 commits2026-07-24: 0 commits2026-07-25: 2 commits2026-07-26: 1 commit2026-07-27: 3 commits2026-07-28: 2 commits2026-07-29: 1 commit2026-07-30: 3 commits2026-07-31: 3 commits2026-08-01: 5 commits2026-08-02: 4 commits2026-08-03: 4 commits2026-08-04: 5 commits2026-08-05: 3 commits2026-08-06: 6 commits2026-08-07: 7 commits2026-08-08: 0 commits2026-08-09: 3 commits2026-08-10: 1 commit2026-08-11: 5 commits2026-08-12: 1 commit2026-08-13: 0 commits2026-08-14: 2 commits2026-08-15: 2 commits2026-08-16: 3 commits2026-08-17: 0 commits2026-08-18: 2 commits2026-08-19: 34 commits2026-08-20: 0 commits2026-08-21: 0 commits2026-08-22: 1 commit2026-08-23: 4 commits2026-08-24: 0 commits2026-08-25: 2 commits2026-08-26: 4 commits2026-08-27: 1 commit2026-08-28: 0 commits2026-08-29: 3 commits2026-08-30: 3 commits2026-08-31: 1 commit2026-09-01: 0 commits2026-09-02: 6 commits2026-09-03: 6 commits2026-09-04: 0 commits2026-09-05: 0 commits2026-09-06: 4 commits2026-09-07: 5 commits2026-09-08: 1 commit2026-09-09: 1 commit2026-09-10: 2 commits2026-09-11: 0 commits2026-09-12: 0 commits2026-09-13: 0 commits2026-09-14: 4 commits2026-09-15: 3 commits2026-09-16: 5 commits2026-09-17: 0 commits2026-09-18: 0 commits2026-09-19: 0 commits2026-09-20: 0 commits2026-09-21: 7 commits2026-09-22: 0 commits2026-09-23: 0 commits2026-09-24: 0 commits2026-09-25: 0 commits2026-09-26: 0 commits2026-09-27: 2 commits2026-09-28: 1 commit2026-09-29: 5 commits2026-09-30: 0 commits2026-10-01: 3 commits2026-10-02: 2 commits2026-10-03: 1 commit
301 commits in the last yearLessMore

Signals and awards

derived from tracked data
  • Widely adopted

    44,877 stars

  • Actively maintained

    Pushed within 48 hours

  • Well documented

    High community health score

  • Permissive license

    MIT

  • Continuous integration

    Automated checks passing

  • Repeat trending

    12 trending appearances

What ai-job-search does

AI Job Search is a local workflow built on Claude Code designed to automate and enhance the job application process. It systematically scrapes job portals, evaluates open positions against a user's pre-configured profile, and generates highly tailored CVs and cover letters using LaTeX. The framework utilizes a multi-agent approach, employing drafter and reviewer agents to refine application materials before final output. It also includes comprehensive tools for tracking applications in Notion, monitoring Gmail for interview invites, generating offline HTML dashboards, and providing customized interview preparation based on the specific job requirements.

Job seekers, developers, and professionals looking for a systematic, privacy-first, and highly automated approach to managing their career search.

  • Automated Job Scraping: Searches multiple job portals, deduplicates listings, and ranks them based on a rigorous fit evaluation framework.
  • Tailored Document Generation: Drafts and refines custom CVs and cover letters compiled via local LaTeX distributions for professional formatting.
  • Multi-Agent Review Pipeline: Implements a drafter-reviewer dynamic to critique and revise application materials before presenting the final version.
  • Ecosystem Integration: Syncs application statuses with Notion databases and monitors Gmail to automatically track progress and interview invitations.
  • Interview Preparation: Generates stage-specific prep packs, researches companies, and conducts mock interviews mapping questions to the user's STAR examples.

Where teams use it

High-Volume Applying

Job seekers batch-score scraped listings to prioritize the best fits, then automate the drafting of tailored applications for the top candidates.

Application Tracking

Candidates maintain a synchronized Notion dashboard and local HTML report to monitor the status of dozens of ongoing applications.

Interview Readiness

Before a scheduled interview, a user runs the prep command to practice answering role-specific questions with an AI interviewer.

Skill Gap Analysis

Users identify missing competencies by having the system analyze untracked job postings and generate a targeted learning plan.

Getting started: gh repo fork MadsLorentzen/ai-job-search --clone

README

master branch

Pip, the courier bird

AI Job Search

The job search that runs on your machine.

MadsLorentzen%2Fai-job-search | Trendshift

CI

An AI-powered job application framework built on Claude Code. Fork it, fill in your profile, and let Claude evaluate job postings, tailor your CV, write cover letters, and prepare you for interviews.

Note: This is an independent open-source project and is not affiliated with, endorsed by, sponsored by, or maintained by Anthropic. Anthropic and Claude Code are referenced only to describe the toolchain this workflow uses.

This project has no affiliated cryptocurrency, token, or paid sponsorship program. Anything claiming otherwise is unauthorized and should be treated as a scam. The only ways to support the project are the Ko-fi link below and contributing on GitHub.

Does it actually work?

I'm a geophysicist by training. When my position was cut in late 2025, I built this framework to run my own job search - the same /scrape, /apply, and /interview workflow in this repo, used weekly, on my own career. I was upfront about it with every employer I spoke to, and instead of counting against me, it usually sparked a genuine technical conversation.

Sixty-nine tailored applications, twenty first interviews, and one signed contract later, I started as an AI engineer in June 2026. People kept asking whether this actually works. It got me hired. Now it's yours.

The longer version, including the full application funnel, is on LinkedIn.

Did this save you a Sunday of cover-letter writing? Consider a coffee.
Did it land you the job? Maybe two.
☕

Buy me a coffee at ko-fi.com

What this is

A structured workflow that turns Claude Code into a full-stack job application assistant. The core workflow (self-profiling, fit evaluation, and the drafter-reviewer application pipeline) is language- and country-agnostic. The job portal search skills are built for the Danish market (Jobindex, Jobnet, Akademikernes Jobbank, etc.), but the pattern is designed to be swapped for your local job boards.

/setup          /scrape              /apply <url>
  |                |                     |
  v                v                     v
Fill in        Search job           Evaluate fit
your profile   portals              Score & recommend
  |                |                     |
  v                v                     v
Profile        Present matches      Draft CV + Cover Letter
files ready    with fit ratings     (LaTeX, tailored)
                   |                     |
                   v                     v
               Pick a match         Reviewer agent critiques
               -> /apply            -> Revise -> Final output

The framework encodes career guidance best practices, including structured evaluation criteria, forward-looking cover letter framing, and optional salary benchmarking.

Prerequisites

  • Claude Code (CLI). Claude Code has no free tier: you need a Claude Pro/Max/Team subscription or Anthropic API credits (pay-per-token, usually cheaper for occasional use). Using a different agent tool (Codex, Antigravity, Gemini CLI)? Start at AGENTS.md - the portal search skills work there out of the box, and community forks adapt the full workflow.
  • Python 3.10+
  • Bun (for job search CLI tools)
  • LaTeX distribution with lualatex and xelatex: TeX Live, MacTeX, TinyTeX, or MiKTeX. The CV compiles with lualatex (pdflatex often fails on modern MiKTeX installs with fontawesome5 font-expansion errors); the cover letter compiles with xelatex because cover.cls requires fontspec. If using a minimal TeX install such as TinyTeX or BasicTeX, install the extra packages listed in SETUP.md.
  • Optional: pip install pypdf for /apply's ATS parseability check (BSD; no Poppler required). Poppler pdftotext remains a fallback (macOS: brew install poppler, Debian/Ubuntu: apt install poppler-utils, Windows: choco install poppler). If both are missing, the check degrades to a visual keyword review.

Quick start

🎥 Prefer to see it in action first? The Next New Thing did a hands-on walkthrough of how the workflow is actually used, from setup to a finished application (recorded August 2026 - commands may have evolved since).

1. Fork and clone

gh repo fork MadsLorentzen/ai-job-search --clone
cd ai-job-search

Important

A fork of this repo is always public — GitHub does not allow private forks of public repositories — and /setup (step 3 below) writes your personal data (name, contact details, employment history, salary expectations) into tracked files. If this copy is for your own job search rather than for contributing changes back, use a private repository with this repo as upstream instead — the two-minute recipe is in SETUP.md section 8, and every update workflow works identically. Fork only to contribute.

2. Install job search tools

PowerShell:

$tools = @("jobbank-search", "jobdanmark-search", "jobindex-search", "jobnet-search", "linkedin-search", "freehire-search")
foreach ($tool in $tools) {
  Push-Location ".agents/skills/$tool/cli"
  bun install
  Pop-Location
}

Bash / zsh / Git Bash:

for tool in jobbank-search jobdanmark-search jobindex-search jobnet-search linkedin-search freehire-search; do
  (cd .agents/skills/$tool/cli && bun install)
done

For linkedin-search and freehire-search the install is optional: both have zero runtime dependencies and run with plain bun; bun install only pulls TypeScript dev types.

3. Set up your profile

claude
# Then inside Claude Code:
/setup

/setup offers three paths: read your documents/ folder if you have one populated (CV PDF, LinkedIn export, diplomas, reference letters, past applications), import a single CV pasted in chat, or walk through an interview. It auto-detects what you have and asks. Documents-folder mode is idempotent and safe to re-run as you add more material; see documents/README.md for the layout.

4. Search for jobs

/scrape

This searches multiple job portals for positions matching your profile, deduplicates results, and presents them sorted by fit. Pick a match to run /apply on it directly — or, when a scrape returns more jobs than you want to eyeball, run /rank to batch-score them all against the fit framework and get a ranked shortlist first.

5. Apply to a job

/apply https://jobindex.dk/job/1234567

If the URL can't be fetched (some job portals block automated access), you can paste the job description directly instead:

/apply <paste the full job description here>

This runs the full workflow: evaluate fit, draft CV + cover letter, review with a second agent, revise, and present the final output.

Postings are treated as untrusted input (the workflow follows no instructions embedded in them and fetches no links from their body), but agentic defenses are instruction-level, not a sandbox - on an unfamiliar job board, skim what was fetched and written before you hit send. Details in SECURITY.md.

Other commands

/setup, /scrape, and /apply form the core workflow. Ten more commands extend it once your profile is in place:

  • /interview preps you for a scheduled interview on a tracked application. It builds a stage-specific prep pack from the application's archive (the exact posting, the CV and cover letter the interviewer actually read, feedback recorded from earlier rounds), researches the company and interviewers with a verify-before-use rule, maps likely questions to your STAR examples, and offers a mock interview following the roleplay protocol in 07-interview-prep.md. Gaps get honest bridge answers, never invented experience.
  • /outcome records what happened to an application - interview stages, offers, rejections, silence. It archives the submitted CV, cover letter, and posting text into documents/applications/<company>_<role>/, keeps outcome.md in the format /setup Path A parses, and updates the tracker. It also owns the stretch before there is an outcome to record: /outcome followup surfaces open applications that have gone quiet (default 10 days), drafts a short channel-appropriate follow-up in your writing style using only claims from the materials you already submitted (drafts only, never sends; at most twice per application), and offers a thank-you note in the same turn an interview stage is recorded. Once a few applications resolve, it points you back to /setup to calibrate the fit framework from what actually got interviews.
  • /notion-sync publishes a one-way, read-only view of the pipeline into a Notion database via the official Notion MCP server (OAuth, no API keys) - one row per ranked job plus every tracked application, with a write-once briefing page per row. The repo files stay the system of record: nothing syncs back, and documents sync as filenames only. Complements /html-report: that is the deep offline dashboard you regenerate at your desk; this is the glanceable live view from anywhere Notion runs (desktop, web, phone).
  • /gmail-sync reads your Gmail (via the Gmail connector) for status signals on your open applications - interview invites, assessment links, offers, rejections - and proposes them as a batch for you to approve before anything is written to the tracker or outcome.md, citing the source email on every proposed change. Offers stop short of proposing hired/offer_declined since that's your call; conflicting or unmatched signals get flagged for a manual /outcome pass instead of guessed.
  • /rank bridges /scrape and /apply: it batch-scores all newly scraped postings against the fit framework (parallel agents fetch each posting and score the five evaluation dimensions) and returns a ranked shortlist with honest per-job strengths and gaps. Deal-breakers veto, deadlines get urgency flags, dead postings get marked expired. Pick a number and it hands off to the full /apply workflow.
  • /expand enriches your profile by scanning public sources you've already linked in it (GitHub repos, portfolio site, Kaggle, Google Scholar) and looking up syllabi for named courses and certifications. Discovered competencies are added to your profile with a source tag. Useful right after /setup to surface skills that documents alone don't make explicit.
  • /upskill analyzes the gap between your profile, your tracked job postings, and your ranked-but-untracked postings (/rank's recorded gaps in seen_jobs.json) — or a single posting via /upskill <URL>. Produces a prioritized heatmap of skill gaps and a learning plan with web-searched study resources and time estimates. Useful for career planning between applications.
  • /html-report generates a self-contained HTML dashboard from job_search_tracker.csv and the application archives — stat cards, status/sector/channel/funnel charts (inline SVG, no external dependencies), and a filterable applications table. Opens directly in a browser, fully offline. Re-run it any time after /apply or /outcome adds new entries.
  • /add-template registers your own CV or cover letter template (LaTeX, Typst, or another toolchain) in place of the stock ones. It captures the template's instructions (source extension, compile command, fonts, style rules, page limit), runs a mandatory test compile, and wires the template into /apply. See Custom templates below.
  • /add-portal generates a job-portal search skill for a job board in your market. It investigates the portal (search URL pattern, result structure, access rules), scaffolds the CLI skill from the same structure as the shipped ones, and test-runs a live query before registering. See Job search tools below.

/reset is also available, see Starting over below.

File structure

ai-job-search/
├── CLAUDE.md                          # Main candidate profile + workflow rules
├── .claude/
│   ├── commands/
│   │   ├── apply.md                   # /apply workflow (drafter-reviewer)
│   │   ├── setup.md                   # /setup onboarding (documents folder, CV import, or interview)
│   │   ├── expand.md                  # /expand competency enrichment from documents and online presence
│   │   ├── add-template.md            # /add-template register custom templates (LaTeX, Typst, ...)
│   │   ├── add-portal.md              # /add-portal generate a job-portal search skill for your market
│   │   ├── rank.md                    # /rank triage scraped jobs into a ranked shortlist
│   │   ├── outcome.md                 # /outcome record application results, archive materials
│   │   ├── gmail-sync.md              # /gmail-sync auto-detect application status from Gmail
│   │   ├── interview.md               # /interview stage-specific prep pack + mock interview
│   │   ├── html-report.md             # /html-report generate application tracker dashboard
│   │   ├── notion-sync.md             # /notion-sync one-way pipeline view in a Notion database
│   │   └── reset.md                   # /reset wipe profile data or documents folder
│   ├── skills/
│   │   ├── job-application-assistant/  # Core application skill
│   │   │   ├── SKILL.md               # Skill definition
│   │   │   ├── 01-candidate-profile.md # Your education, experience, skills
│   │   │   ├── 02-behavioral-profile.md# PI/DISC/personality assessment
│   │   │   ├── 03-writing-style.md    # Tone, structure, do's and don'ts
│   │   │   ├── 04-job-evaluation.md   # Scoring framework for job fit
│   │   │   ├── 05-cv-templates.md     # LaTeX CV structure + tailoring rules
│   │   │   ├── 06-cover-letter-templates.md # LaTeX cover letter templates
│   │   │   └── 07-interview-prep.md   # STAR examples + interview framework
│   │   ├── job-scraper/               # Job search orchestration
│   │   └── upskill/                   # /upskill skill gap analysis and learning plan
│   └── settings.json                  # Claude Code permissions (shared, scoped)
├── .agents/skills/                    # Job portal CLI tools
│   ├── jobbank-search/                # Akademikernes Jobbank (Denmark)
│   ├── jobdanmark-search/             # Jobdanmark.dk (Denmark)
│   ├── jobindex-search/               # Jobindex.dk (Denmark)
│   ├── jobnet-search/                 # Jobnet.dk (Denmark, government portal)
│   ├── linkedin-search/               # LinkedIn public job listings (country-agnostic)
│   └── freehire-search/               # freehire.me tech job aggregator (multi-market, REST API)
├── cv/
│   └── main_example.tex               # moderncv LaTeX template
├── cover_letters/
│   ├── cover.cls                      # Custom cover letter LaTeX class
│   ├── cover_example.tex              # Example cover letter (structural reference + CI smoke test)
│   └── OpenFonts/                     # Lato + Raleway fonts
├── templates/                         # Custom templates registered via /add-template
│   └── README.md                      # Folder layout instructions
├── documents/                         # Career source materials for /setup Path A and /expand
│   ├── README.md                      # Folder layout instructions
│   ├── cv/                            # Master CV (PDF or .tex)
│   ├── linkedin/                      # LinkedIn profile export (PDF)
│   ├── diplomas/                      # Degree certificates and transcripts
│   ├── references/                    # Reference letters
│   └── applications/                  # Past application records (<company>_<role>/)
├── .github/workflows/ci.yml           # CI: LaTeX smoke compiles, skill lint, CLI typechecks
├── salary_lookup.py                   # Salary benchmarking tool (BYO data)
├── tools/
│   ├── check_framework_version.py     # CI check: framework_version bumped when skill files change
│   ├── check_upstream_updates.py      # Preview which personalized files an upstream update touches
│   ├── convert_salary_excel.py        # Convert salary Excel to JSON
│   ├── lint_skills.py                 # CI lint for skills, commands, settings.json
│   ├── robots_check.py                # Gate the browser-header retry against robots.txt
│   ├── security_guards.py             # CI guards: permission allowlist, gitignore rules, manifests
│   ├── upstream_triage.py             # Sort upstream commits into worth-reviewing vs probably-skip
│   ├── verify_layout.py               # Measure a compiled PDF's page layout (holes, orphans, footer collisions)
│   ├── verify_pdf.py                  # Verify a compiled PDF's page count and extractable text
│   └── README_SALARY_TOOL.md          # Salary tool setup instructions
├── job_scraper/                       # Scraper state (seen jobs, results)
├── gmail_sync/                        # /gmail-sync state (processed message IDs, last sync date)
├── upskill/                           # /upskill report output (markdown reports per run)
├── job_search_tracker.csv             # Application tracking spreadsheet
└── SETUP.md                           # Detailed setup guide

How /apply works

The /apply command runs a drafter-reviewer workflow with mandatory PDF compilation:

  1. Parse the job posting (URL or text)
  2. Evaluate fit against your profile (skills, experience, culture, location, career alignment)
  3. Draft a tailored CV and cover letter in LaTeX
  4. Spawn a reviewer agent that researches the company and critiques the drafts
  5. Revise based on the reviewer's feedback
  6. Compile and inspect both PDFs: lualatex for the CV, xelatex for the cover letter. Claude reads the rendered pages and iterates on the LaTeX until the CV is exactly 2 pages with no orphaned entry titles, and the cover letter is exactly 1 page with the signature visible and fonts consistent.
  7. ATS-check the CV: extract the PDF's text layer (pdftotext, optional dependency) and verify it the way an ATS parser sees it — contact details present as literal text, no garbled glyphs, sane reading order — then score the posting's keyword coverage against the extraction. Keywords the profile genuinely supports get added; genuine gaps stay visible, never stuffed.
  8. Present the final output with a verification checklist

All claims in the CV and cover letter are verified against your actual profile. The system never fabricates skills or experience.

What makes this workflow different

  • PDF verification loop. Most LaTeX-resume templates produce "looks fine in the .tex" output that breaks in the PDF: job titles orphan to the next page, cover letters spill onto page 2, bullet fonts silently fall back to the body font. The /apply command compiles and visually inspects every PDF and applies targeted fixes (\needspace, \enlargethispage, font-matching wrappers for list items) until the layout is clean. This runs automatically on every application.
  • ATS verification on the PDF text layer. An ATS reads the PDF's embedded text, not the rendered page — and LaTeX can silently produce PDFs whose text extracts as garbage (icon glyphs where the email should be, interleaved lines from multi-column layouts). /apply extracts the compiled CV's text layer with pdftotext and verifies contact details, reading order, and the posting's keyword coverage against what a parser actually sees. Honesty rule enforced: a keyword the profile doesn't support is acknowledged as a gap, never stuffed in.
  • Relevance-weighted CV cutting. When a CV overflows 2 pages, the workflow does not cut mechanically from the "oldest" section. It scores each candidate line by (a) relevance to the target posting, (b) uniqueness in the document, and (c) whether the cover letter depends on it, and cuts the lowest-total-score line first. An older-role bullet that hits posting keywords survives ahead of a recent-role bullet that does not.
  • Drafter-reviewer separation. The drafter writes; a second Claude agent, spawned with a fresh context, researches the company and critiques the drafts. The drafter then revises. This catches missed keywords, weak framing, and generic language that a single pass often leaves in.
  • Token-efficient reviewer dispatch. The reviewer agent receives drafts inline rather than re-reading them, and the verification checklist runs once at the end of the workflow rather than being duplicated by both agents. Note: the new compile-and-inspect step in Step 5 spends some of those savings on PDF rendering and layout iteration — the workflow trades some end-to-end token cost for a real reduction in broken PDFs reaching the user.

Customization

Which files to edit manually

If you prefer editing files directly instead of using /setup:

File What to change
CLAUDE.md Your full profile (name, education, experience, skills, goals)
01-candidate-profile.md Structured version of your CV data
02-behavioral-profile.md Your behavioral assessment or self-assessment
04-job-evaluation.md Skill match areas, career goals, motivation filters
05-cv-templates.md Profile statement templates for different role types
07-interview-prep.md Your STAR examples from actual experience
search-queries.md Job search queries for your skills and location

Updating your search queries

As your priorities evolve, you can reconfigure just the job search without re-running the full profile setup:

/setup --section search

This re-runs the search configuration interview: which roles to target, which skills to search for, which locations, and which portals. It also suggests role types you may not have considered based on your profile.

Custom templates

The CV uses moderncv (banking style). The cover letter uses a custom cover.cls with Lato/Raleway fonts. Both are LaTeX — the reference engine this repo ships and maintains.

To use your own template instead — LaTeX, Typst, or any other toolchain that compiles to PDF from the command line — run:

/add-template

Point it at your source file (a .tex file plus any .cls/.sty files or bundled fonts; a .typ file plus any local packages; or an equivalent for another toolchain). The command interviews you for the template's instructions — source extension, compile command, fonts and where they live, style rules to preserve, hard page limit — stores everything under templates/, runs a mandatory test compile, and activates the template so /apply drafts and compiles from it. Templates are stored with [PLACEHOLDER] tokens instead of personal data, so they're safe to commit and share.

  • /add-template --list shows registered templates
  • /add-template --use <name> switches between them
  • /add-template --use default reverts to the stock moderncv / cover.cls templates

If you prefer doing it by hand, the manual route still works: update the guidance in 05-cv-templates.md and 06-cover-letter-templates.md.

Job search tools

The four Danish CLI tools in .agents/skills/ (Jobbank, Jobdanmark, Jobindex, Jobnet) demonstrate the pattern for building a job-portal integration for a specific market. If you're in a different country, run:

/add-portal

Give it your local job board's URL. The command investigates the portal (search-URL pattern, result-page structure, robots.txt/access rules), scaffolds a CLI skill with the same structure, commands, and output contract as the shipped ones, and test-runs a live query before registering anything. Auth-walled portals are declined, and portals with restrictive terms get a prominent personal-use-only warning in the generated skill. The generated skill is market-specific and lives in your fork; the generator itself is the universal part.

Maintaining a fork adapted to your market or language? Add it to the Community forks & adaptations thread so others can find it.

For country-agnostic starting points outside Denmark, the repo ships two portal skills alongside the Danish demos:

  • linkedin-search — built on LinkedIn's public, unauthenticated jobs-guest endpoints. Field-agnostic, zero runtime dependencies (runs with just bun), and takes the search location as an explicit flag, so it works for any market out of the box (-l "Berlin, Germany", -l "Mumbai, Maharashtra, India", -l "Remote", …). Intended for personal use only — automated access is against LinkedIn's Terms of Service, so keep volume low. See .agents/skills/linkedin-search/SKILL.md.
  • freehire-search — queries the freehire.me aggregator's public REST API (JSON, no API key). Tech-focused (software, data, engineering, DevOps, remote), multi-market via facet flags (--region, --country, --remote), and zero runtime dependencies. Unlike the HTML-scraping Danish portals, results come back structured (skills, seniority, category). The backend is MIT-licensed and self-hostable — point FREEHIRE_API_URL at your own instance if you prefer. See .agents/skills/freehire-search/SKILL.md.

Extending the framework: portals, templates, criteria - and borrowing from other forks

Everything above adds up to an extension model, so here it is stated plainly. The framework has three extension points, and none of them require touching upstream:

  1. Portal skills - the module system for job boards. Every *-search skill is a self-contained folder under .agents/skills/ with the same contract (a search/detail CLI, --format json|table|plain output, an enabled: flag in its SKILL.md, its own tests). /scrape auto-discovers any installed skill that follows the contract - nothing to register, nothing to wire up. /add-portal generates new ones; the community portal index catalogs the ones other forks have built.
  2. Document templates - /add-template registers any CV or cover-letter toolchain that compiles to PDF from the command line, LaTeX or otherwise.
  3. Evaluation criteria - deal-breakers and preferences in your profile are free-form, and the evaluation rubric scores against whatever you put there. "Strong parental-leave terms", "minimum salary X per my union's scale", "no on-call" - each is one profile line, no code, and it carries real weight in /rank and /apply fit evaluations. Language is the one deal-breaker type with dedicated, structured handling: /setup captures every language you work in and your level (asked directly, or inferred from your CV/LinkedIn export) into a Languages table, and the Language Gate (04-job-evaluation.md) hard-rejects a posting that requires a language you haven't declared at all, while flagging - not auto-rejecting - one that asks for a higher level than you declared in a language you do work in, so a borderline case (a strict "fluent" bar against your own B1/B2, say) gets your judgment instead of a silent drop.

Borrowing a portal skill from another fork is the intended way to get a board that upstream doesn't ship: find it in the portal index, open that fork, and copy the one folder into your own .agents/skills/. Before you run it:

  • Read the code. All of it - these CLIs run pre-approved on your machine (.claude/settings.json allowlists them) against your career data. Check that the only network calls go to the job board it claims to search, that package.json has no dependencies and no lifecycle scripts (postinstall etc.), and that nothing reads or writes outside its own folder.
  • Run its tests offline (bun test in the skill's cli/ directory) - a well-built skill's tests pass with no network access.
  • Check the enabled: flag and the skill's own ToS notes.

The copy step is manual on purpose. Your settings already allow installed portal skills to run without asking each time - so an installer that fetched them from third-party repos for you would skip the one check that matters: you, reading the code first. There isn't one, and that's a security decision rather than a missing feature.

Market-specific data sources (a national salary database, local award-rate tables) follow the same pattern as portals: they belong in a market fork, shared via #78, not upstream.

Salary benchmarking

The salary tool works with any salary data you provide (union statistics, Glassdoor exports, personal research, etc.). See tools/README_SALARY_TOOL.md for the expected format and setup. If you don't have salary data, the salary step is simply skipped.

Starting over

To wipe your profile data and start fresh:

/reset profile    # clears skill files, preserves framework rules
/reset documents  # deletes files from documents/ folder
/reset all        # both

/reset shows exactly what will be deleted and requires you to type RESET to confirm. Nothing is deleted until you do.

Staying up to date

Upstream moves fast. Rather than pulling raw master and hoping, update your fork to a tagged release - a vetted checkpoint described in CHANGELOG.md. python3 tools/check_upstream_updates.py previews exactly which of your personalized files an update touches before you merge, and python3 tools/upstream_triage.py sorts the commits you're behind into "worth reviewing" vs "probably skip" (a weekly workflow can post this to a rolling issue). Full walkthrough in SETUP.md, section 8.

Tips for better results

Profile depth matters

The single biggest factor in output quality is how much detail you put into your profile. A thin profile produces generic applications; a detailed one enables genuinely tailored results.

  • Role descriptions: Don't just list job titles. Describe what you actually did in each position: specific projects, tools used, responsibilities, and measurable achievements. The more material you provide, the more precisely the system can reframe your experience for different roles.
  • Skills in context: Instead of listing "Python" or "project management," describe how and where you applied them. "Built ML pipelines for customer churn prediction in Python using scikit-learn" gives the system far more to work with than "Python, machine learning."
  • All onboarding paths work: Whether you point /setup at your documents/ folder, paste a single CV, or walk through the interview, the principle is the same: richer input produces sharper output.

Career path discovery

The framework supports two distinct modes of job searching:

  • Explicit targeting: You know which roles or sectors you want. The system helps refine and prioritize based on fit.
  • Latent opportunity discovery: By analyzing your full history (not just job titles, but the actual work you did), the system can surface career paths you haven't considered. Transferable skills that map to unexpected industries, patterns in what you enjoyed or excelled at, or emerging roles that combine your domain expertise with new technology.

To get the most from this, invest time during /setup in describing not just your experience, but what energized you, what drained you, and what you'd want more of. This context directly shapes how the system evaluates fit and which roles it surfaces during /scrape.

Contributing

Thinking about a PR? Read CONTRIBUTING.md first - it explains what gets merged, what lives in forks, and why.

Acknowledgements

License

MIT

View on GitHub

Recent activity

commits and pull requests

Releases and announcements

10 total
  1. A patch release with one theme: **the checks the workflow documented now actually run, and the tools stop breaking on anything that is not ASCII.** Compiled PDFs get measured instead of eyeballed, the portal CLIs stop trusting the digits in a URL, and every Python tool survives a Cyrillic posting or a Turkish robots.txt on Windows. Thirty-two entries, twenty-four of them fixes, most reported and fixed by contributors within days of each other. ## Highlights ### `/apply` measures the PDF instead of looking at it (#378, #476, #458, #498, #465) `tools/verify_layout.py` measures what Step 5b previously only eyeballed: per-page text extent, bottom whitespace, the largest internal gap, footer collisions, and an entry header stranded at a page break. It exists for a failure every other check missed: a moderncv `\cventry` is unbreakable, so an entry that does not fit jumps to the next page and leaves a hole while the document still compiles, still has the right page count, and still passes `verify_pdf.py`. Step 5b now also runs the page-count check it had been claiming Step 5d ran (nothing in the workflow passed `--pages` at all). `verify_pdf.py --contains` sees through LaTeX's typograph

  2. A patch release with one theme: **the workflow stops paying for work it does not need and stops hiding work it failed to do.** `/rank` no longer drags your whole job history through the conversation on every run, the portal CLIs now refuse bad input instead of silently returning the wrong thing, and forks get a clean CI and a complete `/setup`. Seventeen entries, fourteen of them fixes, most of them reported and fixed by contributors within a day of each other. ## Highlights ### `/rank` is bounded and keeps its state on disk (#395, #424, #425, #390) A bare `/rank` used to score the entire backlog in one go and read the whole of `seen_jobs.json` into the conversation twice per run - a cost that grew for the life of a workspace and was paid regardless of batch size (one reporter measured a 188 KB state file). `--limit <N>` now bounds each run to ten jobs by default, independently of `--top`, and reports what it deferred. `tools/rank_state.py` owns the state traffic: `candidates` selects and projects only the fields a scoring agent needs, `sweep` runs the expiry pass on disk, and `apply` writes results back atomically. Scoring policy is unchanged, and every existing write-back rule

  3. Two themes this release: **your job search stays yours**, and **stored state tells the truth about what it holds**. The headline fix closes a real privacy leak reported and fixed by the community within a day, and stored jobs now know when their posting has died. ## Highlights ### Fork clones no longer file your private tracker on the upstream repo (#389, #392) `gh repo fork --clone` - the exact command SETUP.md recommended - sets the **upstream** repo as gh's default, and gh uses the default for *creating issues*. So when your own automation ran `gh issue create` from inside your personalized fork ("file a tracking issue per application" is a natural ask), your application pipeline - target companies, fit scores, personal constraints - was published on a stranger's repo, under your identity, where you can't delete it. Four live instances from two users in one week. SETUP.md now puts `gh repo set-default <you>/ai-job-search` directly in the fork commands with a warning at the point of decision, and a new issue template carries the same heads-up the PR template always had. Reported, diagnosed from primary sources, and fixed by @ayobamiseun. **If your clone predates this release, t

  4. Two themes this release: **things that used to fail silently now fail loudly**, and **the text you wrote actually survives into the documents you send**. Most of it comes from a deep empirical review of the whole template ([#347](https://github.com/MadsLorentzen/ai-job-search/pull/347) - 25 fixes, each carrying the test that would have caught it), alongside a portal-contract series and an onboarding privacy fix from the community. ## Highlights ### All six portal CLIs reject unknown flags (**breaking**, #347) A silently discarded flag is worse than an error: on one portal, a plausible-but-wrong flag name returned the **entire database** - 13,862 postings, none matching - with exit 0 and empty stderr, indistinguishable from a real result set. Every CLI now exits 1 with `{"error", "code": "UNKNOWN_FLAG"}` on a flag it doesn't know, which is the bar `/add-portal` always set for contributed portals. If a script of yours passes a stray flag, it will now hear about it - that's the point. ### Your CV's text now survives into the PDF (#347) Three silent text-loss modes, all with clean compiles and green CI: bullets written as `\item [text]` were parsed as LaTeX optional labels and rende

  5. Two themes this release: **your fork now finds out what upstream did**, and **the security guard watches the one key in `settings.json` it used to ignore**. ## Highlights ### Commit-level upstream triage for forks (#305, #320) `tools/upstream_triage.py` walks the commits your fork is behind and sorts them into "worth reviewing" vs "probably skip". Cherry-picks you already ported drop off on their own (matched by `git patch-id`, no bookkeeping), commits touching files you removed are set aside, and a flat `.github/upstream-wontport.txt` keeps declined SHAs from resurfacing. It ships with a weekly `.github/workflows/upstream-watch.yml` that posts the report to a rolling issue in *your own fork* - guarded to no-op on the upstream template (pinned by a test), `GITHUB_TOKEN` only, and report-only by design: it prints ready-to-run `git cherry-pick` lines but never merges or opens a PR, because on a fork "applies cleanly" isn't "correct". It's the commit-history companion to `check_upstream_updates.py`'s version stamps; SETUP.md 8 now introduces the two side by side. Thanks @anjolok1997 - from design discussion (#305) to shipped PR (#320). ### `.claude/settings.json` hooks are now held

Code frequency

additions and deletions
+6.5K-6.5KWeek of 2026-03-15: +6,539 linesWeek of 2026-03-15: -0 linesWeek of 2026-03-22: +0 linesWeek of 2026-03-22: -0 linesWeek of 2026-03-29: +0 linesWeek of 2026-03-29: -0 linesWeek of 2026-04-05: +0 linesWeek of 2026-04-05: -0 linesWeek of 2026-04-12: +0 linesWeek of 2026-04-12: -0 linesWeek of 2026-04-19: +254 linesWeek of 2026-04-19: -77 linesWeek of 2026-04-26: +1,584 linesWeek of 2026-04-26: -428 linesWeek of 2026-05-03: +0 linesWeek of 2026-05-03: -0 linesWeek of 2026-05-10: +0 linesWeek of 2026-05-10: -0 linesWeek of 2026-05-17: +0 linesWeek of 2026-05-17: -0 linesWeek of 2026-05-24: +0 linesWeek of 2026-05-24: -0 linesWeek of 2026-05-31: +0 linesWeek of 2026-05-31: -0 linesWeek of 2026-06-07: +0 linesWeek of 2026-06-07: -0 linesWeek of 2026-06-14: +26 linesWeek of 2026-06-14: -12 linesWeek of 2026-06-21: +0 linesWeek of 2026-06-21: -0 linesWeek of 2026-06-28: +729 linesWeek of 2026-06-28: -3 linesWeek of 2026-07-05: +6,541 linesWeek of 2026-07-05: -322 linesWeek of 2026-07-12: +3,445 linesWeek of 2026-07-12: -1,184 linesWeek of 2026-07-19: +1,445 linesWeek of 2026-07-19: -172 linesWeek of 2026-07-26: +1,342 linesWeek of 2026-07-26: -242 linesWeek of 2026-08-02: +2,145 linesWeek of 2026-08-02: -130 linesWeek of 2026-08-09: +1,088 linesWeek of 2026-08-09: -64 linesWeek of 2026-08-16: +3,434 linesWeek of 2026-08-16: -337 linesWeek of 2026-08-23: +664 linesWeek of 2026-08-23: -69 linesWeek of 2026-08-30: +1,142 linesWeek of 2026-08-30: -137 linesWeek of 2026-09-06: +3,001 linesWeek of 2026-09-06: -119 linesWeek of 2026-09-13: +1,153 linesWeek of 2026-09-13: -53 linesWeek of 2026-09-20: +570 linesWeek of 2026-09-20: -11 linesWeek of 2026-09-27: +1,176 linesWeek of 2026-09-27: -48 linesWeek of 2026-10-04: +0 linesWeek of 2026-10-04: -0 linesMar 15, 2026Oct 4, 2026
+36.3K lines added, -3.4K removed over the last year.

Commits per week

last 52 weeks
520Week of 2025-10-05: 0 commitsWeek of 2025-10-12: 0 commitsWeek of 2025-10-19: 0 commitsWeek of 2025-10-26: 0 commitsWeek of 2025-11-02: 0 commitsWeek of 2025-11-09: 0 commitsWeek of 2025-11-16: 0 commitsWeek of 2025-11-23: 0 commitsWeek of 2025-11-30: 0 commitsWeek of 2025-12-07: 0 commitsWeek of 2025-12-14: 0 commitsWeek of 2025-12-21: 0 commitsWeek of 2025-12-28: 0 commitsWeek of 2026-01-04: 0 commitsWeek of 2026-01-11: 0 commitsWeek of 2026-01-18: 0 commitsWeek of 2026-01-25: 0 commitsWeek of 2026-02-01: 0 commitsWeek of 2026-02-08: 0 commitsWeek of 2026-02-15: 0 commitsWeek of 2026-02-22: 0 commitsWeek of 2026-03-01: 0 commitsWeek of 2026-03-08: 0 commitsWeek of 2026-03-15: 1 commitsWeek of 2026-03-22: 0 commitsWeek of 2026-03-29: 0 commitsWeek of 2026-04-05: 0 commitsWeek of 2026-04-12: 0 commitsWeek of 2026-04-19: 5 commitsWeek of 2026-04-26: 5 commitsWeek of 2026-05-03: 0 commitsWeek of 2026-05-10: 0 commitsWeek of 2026-05-17: 0 commitsWeek of 2026-05-24: 0 commitsWeek of 2026-05-31: 0 commitsWeek of 2026-06-07: 0 commitsWeek of 2026-06-14: 1 commitsWeek of 2026-06-21: 0 commitsWeek of 2026-06-28: 2 commitsWeek of 2026-07-05: 52 commitsWeek of 2026-07-12: 32 commitsWeek of 2026-07-19: 26 commitsWeek of 2026-07-26: 18 commitsWeek of 2026-08-02: 29 commitsWeek of 2026-08-09: 14 commitsWeek of 2026-08-16: 40 commitsWeek of 2026-08-23: 14 commitsWeek of 2026-08-30: 16 commitsWeek of 2026-09-06: 13 commitsWeek of 2026-09-13: 12 commitsWeek of 2026-09-20: 7 commitsWeek of 2026-09-27: 14 commitsOct 5, 2025Sep 27, 2026
301 commits in the last 52 weeks.

When work happens

weekday and hour
SunMonTueWedThuFriSat036912151821Sun 0:00 — 0 commitsSun 1:00 — 0 commitsSun 2:00 — 1 commitsSun 3:00 — 0 commitsSun 4:00 — 1 commitsSun 5:00 — 0 commitsSun 6:00 — 0 commitsSun 7:00 — 0 commitsSun 8:00 — 1 commitsSun 9:00 — 1 commitsSun 10:00 — 4 commitsSun 11:00 — 5 commitsSun 12:00 — 2 commitsSun 13:00 — 4 commitsSun 14:00 — 4 commitsSun 15:00 — 1 commitsSun 16:00 — 2 commitsSun 17:00 — 2 commitsSun 18:00 — 2 commitsSun 19:00 — 5 commitsSun 20:00 — 5 commitsSun 21:00 — 0 commitsSun 22:00 — 0 commitsSun 23:00 — 1 commitsMon 0:00 — 3 commitsMon 1:00 — 1 commitsMon 2:00 — 0 commitsMon 3:00 — 0 commitsMon 4:00 — 0 commitsMon 5:00 — 0 commitsMon 6:00 — 0 commitsMon 7:00 — 5 commitsMon 8:00 — 2 commitsMon 9:00 — 1 commitsMon 10:00 — 0 commitsMon 11:00 — 5 commitsMon 12:00 — 0 commitsMon 13:00 — 3 commitsMon 14:00 — 2 commitsMon 15:00 — 2 commitsMon 16:00 — 0 commitsMon 17:00 — 3 commitsMon 18:00 — 4 commitsMon 19:00 — 4 commitsMon 20:00 — 2 commitsMon 21:00 — 6 commitsMon 22:00 — 0 commitsMon 23:00 — 2 commitsTue 0:00 — 3 commitsTue 1:00 — 2 commitsTue 2:00 — 2 commitsTue 3:00 — 0 commitsTue 4:00 — 0 commitsTue 5:00 — 1 commitsTue 6:00 — 0 commitsTue 7:00 — 0 commitsTue 8:00 — 0 commitsTue 9:00 — 1 commitsTue 10:00 — 0 commitsTue 11:00 — 1 commitsTue 12:00 — 0 commitsTue 13:00 — 1 commitsTue 14:00 — 1 commitsTue 15:00 — 2 commitsTue 16:00 — 4 commitsTue 17:00 — 2 commitsTue 18:00 — 5 commitsTue 19:00 — 2 commitsTue 20:00 — 8 commitsTue 21:00 — 4 commitsTue 22:00 — 1 commitsTue 23:00 — 6 commitsWed 0:00 — 2 commitsWed 1:00 — 5 commitsWed 2:00 — 0 commitsWed 3:00 — 0 commitsWed 4:00 — 0 commitsWed 5:00 — 1 commitsWed 6:00 — 5 commitsWed 7:00 — 1 commitsWed 8:00 — 1 commitsWed 9:00 — 3 commitsWed 10:00 — 1 commitsWed 11:00 — 1 commitsWed 12:00 — 0 commitsWed 13:00 — 1 commitsWed 14:00 — 1 commitsWed 15:00 — 1 commitsWed 16:00 — 4 commitsWed 17:00 — 2 commitsWed 18:00 — 1 commitsWed 19:00 — 13 commitsWed 20:00 — 21 commitsWed 21:00 — 8 commitsWed 22:00 — 5 commitsWed 23:00 — 2 commitsThu 0:00 — 0 commitsThu 1:00 — 3 commitsThu 2:00 — 0 commitsThu 3:00 — 0 commitsThu 4:00 — 0 commitsThu 5:00 — 2 commitsThu 6:00 — 1 commitsThu 7:00 — 2 commitsThu 8:00 — 2 commitsThu 9:00 — 1 commitsThu 10:00 — 1 commitsThu 11:00 — 1 commitsThu 12:00 — 0 commitsThu 13:00 — 0 commitsThu 14:00 — 0 commitsThu 15:00 — 1 commitsThu 16:00 — 1 commitsThu 17:00 — 2 commitsThu 18:00 — 5 commitsThu 19:00 — 1 commitsThu 20:00 — 3 commitsThu 21:00 — 1 commitsThu 22:00 — 2 commitsThu 23:00 — 1 commitsFri 0:00 — 2 commitsFri 1:00 — 1 commitsFri 2:00 — 0 commitsFri 3:00 — 2 commitsFri 4:00 — 0 commitsFri 5:00 — 0 commitsFri 6:00 — 0 commitsFri 7:00 — 1 commitsFri 8:00 — 3 commitsFri 9:00 — 0 commitsFri 10:00 — 1 commitsFri 11:00 — 3 commitsFri 12:00 — 1 commitsFri 13:00 — 3 commitsFri 14:00 — 2 commitsFri 15:00 — 2 commitsFri 16:00 — 1 commitsFri 17:00 — 0 commitsFri 18:00 — 2 commitsFri 19:00 — 5 commitsFri 20:00 — 3 commitsFri 21:00 — 3 commitsFri 22:00 — 1 commitsFri 23:00 — 0 commitsSat 0:00 — 0 commitsSat 1:00 — 0 commitsSat 2:00 — 0 commitsSat 3:00 — 0 commitsSat 4:00 — 0 commitsSat 5:00 — 0 commitsSat 6:00 — 1 commitsSat 7:00 — 0 commitsSat 8:00 — 2 commitsSat 9:00 — 0 commitsSat 10:00 — 2 commitsSat 11:00 — 1 commitsSat 12:00 — 0 commitsSat 13:00 — 1 commitsSat 14:00 — 1 commitsSat 15:00 — 1 commitsSat 16:00 — 0 commitsSat 17:00 — 1 commitsSat 18:00 — 1 commitsSat 19:00 — 0 commitsSat 20:00 — 2 commitsSat 21:00 — 6 commitsSat 22:00 — 5 commitsSat 23:00 — 0 commits
Commit volume by weekday and hour (UTC). Larger dots mean more commits.

Who is committing

last 52 weeks
Maintainer commits109 (36%)
Community commits195 (64%)

304 commits in total over the last year.

DateListRankStars gained
Jul 25, 2026daily#7+5
Jul 24, 2026daily#11+4
Jul 20, 2026daily#8+4
Jul 15, 2026daily#8+9
Jul 10, 2026daily#4+4
Jul 9, 2026daily#1+10
Jul 8, 2026daily#1+23
Jul 7, 2026daily#1+25
Jul 6, 2026daily#14+6
Jul 5, 2026daily#19+21
Jul 4, 2026daily#24+19
Jun 4, 2026daily#16+28