AI Job Search
A job-application framework that runs on your own machine: it scores postings against a profile you fill in, drafts a tailored CV and cover letter in LaTeX, compiles them, reads the rendered PDF back — and stops one step short of sending anything.

What it is
Thirteen slash commands turn Claude Code into a job-application pipeline: /setup builds a structured profile from a folder of documents, a pasted CV or an interview; /scrape searches job portals through per-portal CLI skills; /rank batch-scores postings against the profile; /apply runs a drafter-reviewer pair that writes a CV and cover letter in LaTeX, compiles them, inspects the rendered pages, and reads the PDF’s text layer the way an applicant-tracking system would. Six portal skills ship with it — four for the Danish market, plus country-agnostic LinkedIn and freehire integrations — and /add-portal generates new ones. Nothing is ever submitted.
Who built itA geophysicist by training, whose position was cut in late 2025. He built this framework to run his own job search and used it weekly on his own career, and reports the outcome in the README: sixty-nine tailored applications, twenty first interviews and one signed contract, starting as an AI engineer in June 2026. He told every employer he spoke to that the applications were machine-assisted, and writes that this usually started a technical conversation rather than counting against him. He wrote 108 of the 293 commits and still reviews every pull request himself.
How it is put together
The parts · 6There is no server, no database and no build step. The framework is a set of Markdown instructions that a general-purpose coding agent reads, a set of small command-line programs it calls, and a folder of plain files where the state lives — a CSV of applications, a JSON of postings already seen, a directory per submitted application. /scrape finds portal tools by their shape rather than from a registry: any folder under a skills directory that exposes search and detail with the agreed output contract is picked up, so adding a job board means adding a folder and never touching the framework. The split between what is written as an instruction and what is written as a program follows what has to be exact — fetching a portal, counting the pages of a compiled PDF, extracting a PDF’s text layer are all real programs with offline tests, while evaluating fit and framing a decade of experience are prose. The state files are what make the whole thing resumable across sessions, and they are ordinary files in the user’s own repository rather than anything the framework owns.
- .claude/commands/
- Thirteen Markdown files, one per slash command — the workflow itself.
/setup,/scrape,/rankand/applyare the core loop; the rest add interview preparation, outcome tracking, calendar-free follow-ups and report generation. - .claude/skills/job-application-assistant/
- The judgement layer: one skill file plus seven numbered references covering the candidate profile, a behavioural profile, writing style, the fit-evaluation rubric, CV and cover-letter rules, and interview preparation. This is where “never fabricate experience” is enforced.
- .agents/skills/<portal>-search/
- One self-contained folder per job board — a skill file, a
cli/with sources and its own tests, and a URL reference. Six ship: four Danish portals plus LinkedIn and freehire. Each has zero runtime dependencies and a test suite that passes with the network blocked. - tools/
- The mechanical checks: page count and text-layer extraction for a compiled PDF, a layout measurer that reports holes, orphaned entries and footer collisions, a robots.txt gate for the optional browser-header retry, and guards that keep the permission allowlist and gitignore rules intact.
- LaTeX toolchain
- The CV compiles with
lualatexagainst a moderncv template and the cover letter withxelatexagainst a custom class, because the letter’s class needsfontspec. A registration command can swap either for another toolchain that compiles to PDF from a command line, storing templates with fill-in tokens rather than real details, so they carry no personal data. - State files
job_search_tracker.csvfor applications, a JSON of already-seen postings for de-duplication, a directory per submitted application holding the CV, the letter and the posting text as it was at the time, and a sync state for the mail reader. Plain text, in the user’s repository, readable without the framework.
Choices, and what they beat
Portal skills are copied in by hand over an installer that fetches them from other people’s forks
Installed skills are already allowlisted to run without asking on every invocation, so an automatic installer would skip the only check that matters — the user reading the code first. The README states that the absence of an installer is a security decision rather than a missing feature.
The workflow stops one step short of submitting over browser automation against application forms, stored portal credentials, a scheduler that applies
The maintainer calls this line deliberate and permanent, and closed a contributed multi-user web application on those grounds rather than on quality.
A uniform contract per portal, discovered by shape over a registry, or a single scraper with per-site branches
Extension without touching the framework, and a market fork can be shared by copying one folder. The contract is what makes that safe: structured output, an honest user-agent, backoff on rate limits, and a refusal to fall back to a browser user-agent when a site blocks the honest one.
Upstream arrives as tagged releases with a preview step over pulling the upstream branch
Every user has edited their own copy — profile files, evaluation criteria, their own portal skills — so a merge is a personal-data operation. One tool previews which personalised files an update touches and another sorts the incoming commits into worth-reviewing and probably-skip.
Read fromThe README’s file-structure section, its extension-point section and CONTRIBUTING.md.
Build log
7 stages- 01
It got him hired
The README answers the first question a reader has with the author’s own search. He was a geophysicist; his position was cut in late 2025; he built this to run his own applications and used the same
/scrape,/applyand/interviewworkflow that ships in the repository, weekly, on his own career. The figures he reports are sixty-nine tailored applications, twenty first interviews and one signed contract, and he started as an AI engineer in June 2026. He adds the part most projects leave out — that he told every employer he spoke to that the applications were machine-assisted, and that it usually started a genuine technical conversation rather than counting against him. These are the author’s own numbers, and this archive has not verified them; what makes them worth recording is that they are exactly the kind of claim the project’s own method would demand a source for, and the README states them as a personal account rather than as a benchmark. - 02
Fifteen thousand forks, and the trap that creates
The distribution model is a fork. The README’s first instruction is to fork the repository, fill in your profile and own the result; there are 15,313 forks against 44,451 stars, a ratio this archive has not seen elsewhere. The design is coherent — every user is meant to edit their own copy, and upstream changes arrive as tagged releases, with
tools/check_upstream_updates.pypreviewing exactly which personalised files a merge will touch. It also creates the problem the repository states plainly in a warning block: a fork of a public repository is always public, GitHub does not offer private forks, and/setupwrites your name, contact details, employment history and salary expectations into tracked files. So the path the README recommends to everyone else — fork, clone, fill in — is the wrong one for the person it is aimed at. The correct route is a private repository with this one added asupstream, and that recipe is two minutes long and lives in section 8 of a separate document. It is unusual to find a project whose own onboarding instructions are the second-best option, and which says so. - 03
The part that makes it different: it looks at the PDF
A LaTeX résumé can be correct in the source and wrong on the page — a job title orphaned onto the next sheet, a cover letter spilling onto a second one, list bullets silently falling back to the body font when an icon font fails to expand. So
/applycompiles withlualatexfor the CV andxelatexfor the letter, then reads the rendered pages and iterates until the CV is exactly two pages with no orphaned entries and the letter is exactly one with its signature visible. Then it goes a step further and checks the text layer rather than the picture, because that is what an applicant-tracking system parses: contact details present as literal text, no garbled glyphs, sane reading order, and a keyword-coverage score computed against what a parser actually extracts. The honesty rule is written into the workflow — a keyword the profile genuinely supports gets added, and a genuine gap stays visible rather than being stuffed. When a CV overflows, the cut is not taken from the oldest section but scored line by line for relevance to the posting, uniqueness in the document, and whether the cover letter depends on it. The Language Gate does the same job for languages: a posting requiring a language the profile never declares is rejected outright, while one asking for a higher level than declared in a language you do work in is flagged for your judgement rather than silently dropped. - 04
A tracker whose bug fixes are about Unicode and Windows
The issue tracker is where the project’s real engineering shows, and a run of closed pull requests reads as a single theme. A contributor reported that the tracker’s normalisation lowercased company and role names and then removed everything outside
[a-z0-9]— which made two characters with no ASCII form both normalise to the empty string, so an application already tracked at one company was ranked again, and a company whose name normalised away took unrelated roles out of the shortlist with it. The same filter made precomposedCafédisagree with decomposedCafeplus a combining accent. The fix keeps combining marks after normalisation, and the case the maintainer singled out as proving it was a Devanagari pair differing by one such mark. A companion fix handled the job keys: any company name without an ASCII slug became the literal stringunknown-company, so two different companies with the same Latin title produced the same key and the second posting was treated as already seen. Then a third: six tools printed JSON with non-ASCII characters intact to a piped standard output, and on Windows a pipe defaults to the ANSI code page, so a single Cyrillic, Chinese or Devanagari posting crashed the tool outright. The maintainer’s note on that one names the reason it survived so long — continuous integration is Linux-only, and a pipe is how the agent runs every tool — so the whole class of bug is invisible to the suite meant to catch it. - 05
One hard rule, and no mechanical check anywhere in the workflow
The best issue in the tracker is about a deferral nobody ever executed. Two places in the repository stated that page-count enforcement was the job of a verification tool and that another step already ran it — one in the workflow document, the other in the docstring of the layout checker, which added that continuous integration runs it too. A contributor searched the repository for every invocation of that tool with the page-count flag and found the argument definition, the two prose deferrals, and a CI assertion covering only the stock example documents. No runnable step in the actual workflow passed the flag. The CV’s two-page limit and the letter’s one-page limit were therefore being enforced by a model looking at a picture and deciding, with a mechanical check available and unused. The fix added the two calls and four tests; the maintainer confirmed it against the compiled stock documents and named what had been carrying the rule alone. It is a specific kind of failure worth recording, in a repository whose entire claim is that a statement should be checkable: the check existed, the documentation said it ran, and it did not run.
- 06
The line it will not cross
A pull request arrived with a complete multi-user web application — a backend, a front end, per-account isolation, password hashing, a scheduler looping each user independently. The maintainer closed it, and the reason is the most interesting sentence in the tracker: the template prepares an application and always stops one step short of sending it. No browser automation against application forms, no stored portal credentials, no scheduler that applies. He calls that line deliberate and permanent. The same policy runs through the portal skills: a CLI blocked by a bot-protection layer does not spoof a browser user-agent by default, it reports the refusal and points at a documented manual procedure, and a contributed skill describes the block honestly and asks for the honest user-agent to be kept. It also explains a decision that would otherwise read as a missing feature — there is deliberately no installer that copies a portal skill out of somebody else’s fork, because installed skills are already allowlisted to run without asking, so an automatic installer would skip the one check that matters, which is the user reading the code first. And it is why a batch of perfectly competent portal skills was declined: a sector-specific board, the maintainer wrote, leaves no principled reason to refuse the next sector, the same answer country portals get. One of those pull requests would also have written the contributor’s own role targeting — a list naming three-dimensional-art titles and ruling out senior ones — into the shared scraper skill, which would have loaded into every user’s search regardless of their field.
- 07
One commit in March, a hundred and twenty-three in July
The monthly commit counts tell the project’s arc better than any summary: one commit in March, twelve in April, three in June, then 123 in July, 107 in August and 47 in September. The inflection has a date attached. On 2026-07-10 the project topped GitHub’s trending list, gaining more than 3,700 stars in a day and reaching 17,700 in total, and a Chinese developer-community column covered it the following morning with a walkthrough of the profile system, the drafter-reviewer pair and the PDF inspection loop. Before that it was a working personal tool with a handful of commits; twelve days later it had a tagged release,
v1.0.0, and by September it had nine of them — each titled as a claim rather than a version number, from “Language Gate + ATS-safe date fields” through “Upstream that reaches your fork, hooks that can’t hide” to “Cheaper ranking, portals that fail loudly, forks that stop fighting CI”. All nine are full releases; none is a pre-release. Fifty contributors have since touched it, most with a single commit, and the two who stayed are the ones whose names appear on the fixes above.
Adjacent records
All records →No. 061
DeepSeek Harness
DeepSeek’s agent harness, built so that the model adapter, the tool registry, the session log and the agent loop itself are plugins — swapped from a configuration file rather than a fork.
No. 060
VibeGame
Describe a game in one sentence and a team of agents divides the work — an architect plans it, a programmer builds it, an auditor checks the code against the plan, and a player has to actually play it before the task is accepted.
No. 054
gate4agent
A Rust workbench for running, watching and orchestrating CLI coding agents across machines — wrapping Claude Code, Codex, Kimi and Grok behind one transport, one relay and one operator protocol.