智能筛选岗位,一键完成求职全流程
为ai-job-search打分
给出您宝贵的评分:
手机端可长按上方图片保存到相册,或点击「下载/分享」分享到微信
使用 ai-job-search,你可以:
基于Claude Code搭建的自动化求职框架,录入个人资料后自动筛选适配岗位、定制简历、撰写求职信、模拟面试备考。
用户评论 (0)
2026年08月07日
2026年08月05日
2026年08月30日
2026年03月27日
2026年08月14日
2026年09月09日
2026年09月01日
2026年08月21日
2026年08月15日
2026年08月10日
2026年08月08日
v1.7.1
2026年09月07日
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 (legacy location migration, null deadlines never erasing a stored one, verbatim strengths and gaps) is pinned by tests. /rank also now flags postings whose stored posted_date is more than 30 days old.
Portal CLIs fail loudly instead of lying (#426, #429, #371, #416, #418, #421)
The sharpest fix of the release: the unknown-flag guard in the four bunli-based CLIs only inspected --long flags, so an undefined short flag was silently discarded and the search ran unfiltered - search -q "sygeplejerske" on jobnet returned 18,210 ads with exit 0 where the correct flag returns 669. Both dash forms are now checked. The detail commands of jobbank, jobdanmark and jobnet accept full posting URLs as the contract always said they should, linkedin-search rejects fractional numeric flags instead of truncating them, and a one-CLI-at-a-time audit closed three crashes where a single null field from a portal API killed the whole run (a jobbank feed date, a jobnet publication date, a jobdanmark autocomplete item).
Forks get a clean CI and a complete /setup (#405, #433, #396)
The python-tests job ran the upstream-only placeholder-integrity tests on every personalised fork and failed permanently; they now skip when GITHUB_REPOSITORY is a fork and still run locally and upstream. /setup now fills the contact blocks inside 05-cv-templates.md and 06-cover-letter-templates.md - the two templates /apply compiles from - which a full run previously left as [YOUR_NAME] and [YOUR_EMAIL]; /reset restores them. And settings.json no longer pre-approves bun run on arbitrary files: the allowlist is one path-scoped entry per shipped portal CLI.
Salary data tools stop failing silently (#414, and the salary_lookup.py null fix)
convert_salary_excel.py could mistake a source-citation line above a real header for the header itself and drop every company's salary data with exit 0; header detection now requires cross-cell corroboration and warns when a sheet yields no salary columns. salary_lookup.py no longer crashes on a null metadata or categories field.
Fork maintainers: what to reconcile
Pull this tag rather than raw master, then check three things:
/rankbehaviour changed. A bare run now scores at most 10 jobs and reports the rest as deferred; pass a larger--limitif you relied on scoring everything in one go. State moves throughtools/rank_state.py; if you customisedrank.md's Step 1 or Step 4, re-read the new versions before merging your changes over them..claude/settings.jsonchanged twice. The blanketBash(bun run:*)entry is gone in favour of one path-scoped line per shipped CLI, and twopython tools/rank_state.pylines were added. A fork with its own portal skills needs its own path-scopedbun runline per skill (and the matchingtools/security_guards.pyentry), or/scrapewill prompt once per run./setupand/resetcover two more files. If you already personalised05-cv-templates.mdand06-cover-letter-templates.mdby hand, nothing changes; if you have not, re-run/setup --sectionfor the contact blocks or edit them directly.
No framework_version marker changed in this release: the methodology files under job-application-assistant/ are as they were in 1.7.0.
Also in this release
- A CHANGELOG structure guard runs on every PR (duplicate or unknown headings, orphan entries, conflict markers).
/scrapepersists each posting's publication date (#391), which/ranknow consumes.linkedin-search detailaccepts LinkedIn job URLs with trailing slashes, regional subdomains and tracking parameters (#411).- The
documents/interview/**ignore rule no longer claims interview prep is written there;/interviewhas always saved underdocuments/applications/, which was already ignored (#336).
Thanks to everyone who reported and fixed this cycle - ayobamiseun for the null-deref audit across all six CLIs, Na-bra for the salary and flag-guard fixes, InstinctEx for the /rank work and the numbers behind it, iAbhiNaV2005 for the URL normalisation, markvandeven for the fork-CI diagnosis, and shahidbeig-a11y for carrying it.
v1.7.0
2026年08月29日
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, the fix does not reach you automatically - see "Updating a personalized fork" below.
Stored jobs now know when the posting has died (#280, #383)
linkedin-search detail gains an isActive field: false when the posting page renders LinkedIn's own "No longer accepting applications" banner. Detection is scoped to the top card and pinned by fixture tests in both directions - including the false-positive case where recruiter boilerplate quotes the closed phrase inside a description (the unscoped first version flagged it; the shipped version provably does not). /scrape records closed-at-source jobs as "status": "expired" - marked, never silently dropped - which addresses the ghost-LinkedIn-jobs class (#331): an expired LinkedIn URL redirects to a similar live job, so a stored hit could die unnoticed between scrape and click. Diagnosed and first implemented by @navakanth1984, adopted and finished by @ayobamiseun with the original commit preserved.
Fractional flags stop silently changing your search (#373)
freehire-search --jobage 0.5 ("last half day") truncated to 0, which disabled the freshness filter entirely - exit 0, no error, stale postings presented as fresh, on a default-ON /scrape portal. Numeric flags now accept whole numbers of at least 1 and reject everything else with the standard BAD_ARG error, matching the Danish CLIs' validation. Reported with a stubbed-network repro and fixed by @ayobamiseun. (The linkedin-search sibling, #371, is reserved by its reporter and lands separately.)
The ATS text check works on Windows without Poppler (#369)
/apply's mechanical parseability check and tools/verify_pdf.py now extract the CV text layer with pypdf first (BSD-licensed, one pip install), falling back to Poppler's pdftotext, and degrading to a visual review only when both are missing - so the check that catches invisible-to-ATS CVs actually runs on a stock Windows machine. Includes the UTF-8 subprocess-decoding fix that kept Danish characters intact on the fallback path. Contributed by @sdrarunvarshan.
/reset profile now actually clears your profile (#364)
Two of the six files /setup personalizes were left out of the wipe: 04-job-evaluation.md (your match areas, career goals, financial constraints) was explicitly labelled "framework rules, not candidate data" while holding both, and job-scraper/search-queries.md (your boards, titles, city, commute tiers) appeared nowhere in the reset at all. Both are now previewed and cleared, and a CI guard derives the reset list from /setup's own targets so a future forgotten file fails loudly. Reported via his own issue and fixed by @pchukwuemeka424.
Also in this release
- CI tests the full documented Python range (#370) - a 3.10-3.14 matrix replaces the 3.12 pin; grew out of an independent cross-platform verification by @atiqur-rahman-pro.
- Company research is cached between
/applyand/interview- a 30-daycompany_research/cache (gitignored, guarded) stops the same company being researched twice from scratch; verification-before-claims is unchanged, only discovery is cached. - CI compiles the LaTeX examples on Debian bookworm's apt TeX Live as a second leg, so the moderncv 2.3.1 compile fix is guarded in the environment that actually had the bug.
salary_lookup.pynormalizes dotted legal suffixes (#356) - "A.M.B.A." and "amba" now match identically. Thanks @Ritik650.
Updating a personalized fork
Versioned files touched since v1.6.0:
| File | v1.6.0 | v1.7.0 |
|---|---|---|
04-job-evaluation.md |
1.2.4 | 1.2.6 |
05-cv-templates.md |
1.4.2 | 1.4.3 |
Run python3 tools/check_upstream_updates.py for per-file diffs.
Four things to check after pulling:
- Run this today in every existing clone, before anything else:
The SETUP.md fix only reaches clones made after this release. If your clone came from
gh repo set-default <your-username>/ai-job-searchgh repo fork --clone, gh is pointingissue create(and more) at the upstream repo right now. Check withgh repo set-default --view. - Scripts passing fractional values to
freehire-searchnumeric flags now get exit 1 (BAD_ARG) instead of a silently altered query.--jobage 0is also rejected - it was an undocumented "no filter" alias. seen_jobs.jsonmay gain"status": "expired"entries from/scraperuns. No migration needed - it is existing status vocabulary, and expired jobs are marked, never deleted./reset profilenow clears04-job-evaluation.mdandsearch-queries.mdtoo. If you relied on the old (buggy) behavior preserving those two, export them before your next reset.
v1.6.0
2026年08月20日
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 - 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 rendered clipped off the page edge (the shipped example CV lost every experience bullet this way); an unescaped % in a quantified achievement ("cut latency by 40% and saved DKK 2M") silently deleted the rest of the line; and the documented ATS extraction command mis-decoded every non-ASCII character on Xpdf-based pdftotext builds, failing correct CVs. All fixed, a "LaTeX Special Characters" guide added, and CI now asserts body text survives into the PDF text layer.
Every portal speaks the same contract now (#339, #340, #342, #344, #347)
The /scrape search contract (title, company, location, date, url) is now emitted by all six portals (thanks @oscarbol09 for the jobnet/jobdanmark/jobbank series), and deadlines are YYYY-MM-DD or null everywhere: jobbank's DD.MM.YYYY converts (breaking if you parsed the old form), jobindex's "ASAP" maps to null (it means "no stated deadline", which is what null is for), jobnet's 1900-01-01 sentinel no longer leaks from detail, and jobdanmark's HTML fallback stops emitting raw page text and the literal "Løbende" as dates. A repo-level contract pin (@oscarbol09, #344) derives the field list from the spec sentence itself and fails CI if any portal drifts - and /rank now treats any non-ISO deadline already stored in your data as absent rather than guessing.
The jobindex detail command works again (#347)
Jobindex redesigned its pages; every selector the parser used was gone, so detail returned CSS-comment text as the deadline, an external ATS URL as its own id, and null everything - exit 0, on 4 of 5 live postings. Rewritten against both current page shapes; 5/5 live postings now return full descriptions and correct fields.
Onboarding warns about public forks before it's too late (#345, #348)
The quick start walked a new user into gh repo fork - forks of public repos are always public - and two steps later /setup wrote their name, contact details, and salary expectations into tracked files, with the only complete warning buried in the updates section. Reported by @basilevs with a full reproduction after hitting it himself. The warning now sits next to both fork commands, and /setup checks your origin's visibility before writing anything, not after.
Also in this release
seen_jobs.jsonrecords whether each job came from a CLI or the WebSearch fallback (#338) - ghost-job reports are now diagnosable from stored state. Thanks @yshraj, with an empirical community review by @oscarbol09.- Job matching reframed around function, not title -
/setupand the evaluation framework now build priority categories around the kind of problem a role solves, with title variants as query inputs rather than the bet itself. - A
/in a company or role name no longer nests the application archive one level too deep (#329) - the canonical single-path-component rule now reaches all six derivation sites. Thanks @ayobamiseun, building on @jakob1379's independent spec. convert_salary_excel.pyno longer corrupts US-formatted numbers 1000× - locale is decided by which separator appears last, and Danish compound headers pair correctly./html-report's funnel counts stages from history - an application that interviewed and was later rejected now counts as having interviewed, and declined offers stop inflating the rejection rate./gmail-syncsearches beyond the Inbox --in:sent -in:draftsreplacesin:inbox, so archived and label-filtered mail stays in scope./rankpersists its location verdict aslocation_verdict- the barelocationkey meant both a place and a PASS/FAIL/FLAG verdict in the same store; legacy entries are read compatibly./reset documentsnow clearsdocuments/postings/,/upskillhandles rows with no fit rating,/scrapegets a client-side recency fallback for portals without a date filter, andfreehire-searchgains--no-descriptionfor cheap discovery passes (~83% smaller payloads).- The guards are finally guarded -
check_framework_version.py(the fork-safety CI gate) had zero tests and could be disabled by one line; it,lint_skills.py's real checks,robots_check.py's tie-break and browser-UA fallback, and CI's placeholder sentinels (which watched a comment, not the data) are all now covered by discriminating, mutation-verified tests. - CONTRIBUTING: invited PRs are reserved for the invitee.
Updating a personalized fork
Versioned files touched since v1.5.0:
| File | v1.5.0 | v1.6.0 |
|---|---|---|
job-application-assistant/SKILL.md |
1.3.2 | 1.3.4 |
04-job-evaluation.md |
1.2.2 | 1.2.4 |
05-cv-templates.md |
1.4.0 | 1.4.2 |
06-cover-letter-templates.md |
1.0.1 | 1.0.2 |
Run python3 tools/check_upstream_updates.py for per-file diffs, or python3 tools/upstream_triage.py for the commit-level view.
Five things to check after pulling:
- Scripts passing stray flags to portal CLIs now get exit 1 (
UNKNOWN_FLAG) instead of silently unfiltered results. Fix the flag name; the CLI's--helpand SKILL.md list the real ones. - jobbank deadlines are now
YYYY-MM-DD. Anything parsing the oldDD.MM.YYYYsearch output must update. - Expect a small merge conflict in
cv/main_example.texif you personalized it: upstream braced the placeholder bullet lines you replaced. Resolution: keep your own bullets - real prose was never affected by the bug. - Stored
seen_jobs.jsondata needs no migration. Old"ASAP"/DD.MM.YYYYdeadlines are treated as absent by/rank's sweep rather than compared; a legacy PASS/FAIL/FLAG value inlocationis read as the verdict until re-ranking moves it tolocation_verdict. - If your fork is public and personalized, read #345. The new onboarding warnings are for the next user; if your own profile data already reached a public fork, GitHub's sensitive-data-removal process is the reliable path - force-pushing doesn't purge fork networks.
v1.5.0
2026年08月13日
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 to a reviewed allowlist (#313)
The security guard read permissions.allow and nothing else, so a hooks block in the same file passed silently. A hook is strictly more dangerous than a pre-approved permission: it runs unconditionally when its event fires, with no prompt and no model decision in between - the exact vector the Shai-Hulud worm's August 2026 wave used, planting a SessionStart hook in .claude/settings.json. ALLOWED_HOOKS ships empty, the check runs before the permissions shape guards so a malformed block can't skip it, and unrecognised hook layouts fail closed. Thanks @Haseeb-1698.
One tracker status vocabulary, defined once (#298, #299)
The tracker CSV's status column had six independent restatements with inconsistent spellings, and two real bugs to show for it: /html-report silently dropped no response/offer declined rows from the rejection-rate denominator, and /gmail-sync kept chasing applications that were already closed. /outcome (the only writer) now owns the canonical set; every reader references it and accepts both spellings on read. Diagnosis @jakob1379, implementation @oscarbol09.
/apply archives the posting while it still holds it (#306, #307)
The full posting text now lands in documents/applications/<company>_<role>/job_posting.md at draft time - never a re-fetch of a URL the spec itself expects to die, and pasted-text postings finally have a recovery path. Diagnosis and implementation @jakob1379.
Also in this release
- CI discovers portal CLIs dynamically (#310) - a portal added with
/add-portalgets its typecheck and tests run by CI automatically, on this repo and on any fork, no workflow edit. Thanks @ayobamiseun. /upskillreports gitignored at the path the skill actually writes them (#317) - the old rule was rooted; the report (your skill gaps, measured against named employers) could land unignored one directory deeper. New rule pins the report-file prefix at both depths, guard-enforced. Thanks @ayobamiseun.- Phantom
evaluatedstatus dropped fromseen_jobs.json's documented vocabulary (#315, #316) - it never had a writer or a reader. Thanks @ayobamiseun. /add-portalcredential spec (#304) - if a portal is only reachable through a paid fetching service, the command now surfaces the billing before scaffolding, requires the token env-only (<SERVICE>_API_TOKEN), and keeps the whole path explicitly subordinate to the robots/ToS decision. Spec only; no shipped portal needs a credential. Thanks @Haseeb-1698./add-portal's fetching contract line now states the honest-UA posture - generated skills default toMozilla/5.0 (compatible; <portal>-cli/1.0), the convention every shipped CLI follows.
Updating a personalized fork
Versioned files touched since v1.4.0:
| File | v1.4.0 | v1.5.0 |
|---|---|---|
job-application-assistant/SKILL.md |
1.3.0 | 1.3.2 |
Run python3 tools/check_upstream_updates.py for per-file diffs - and from this release, python3 tools/upstream_triage.py for the commit-level view.
Three things to check after pulling:
- Status spellings - if your personalized
/outcomewritesno responseoroffer declined(space forms), switch the write path tono_response/offer_declined. Existing rows keep working; every reader now accepts both forms on read. upstream-watch.ymlarrives in-tree - it no-ops on the upstream template and only runs on your fork if you enable Actions, so activation stays a deliberate act. Don't want it? Delete the file or add its SHA to.github/upstream-wontport.txt.- New required gitignore rule -
**/upskill/report-*.mdis now inREQUIRED_IGNORE_RULES; if your fork trimmed.gitignore,security_guards.pywill name what's missing.
Thanks
@anjolok1997, @Haseeb-1698, @ayobamiseun, @jakob1379, @oscarbol09 - five contributors, and every item above started as a community issue, discussion, or PR.
详细ChangeLogv1.4.0
2026年08月07日
Why update
Two silent failures in the flagship flow are fixed:
Your applications no longer vanish. /apply wrote your CV and cover letter to disk and then recorded nothing - a drafted-and-submitted application was invisible to /gmail-sync, /html-report, /notion-sync, /interview, and /rank's dedup, and nothing ever detected the loss (#269, fixed in #291 by @jakob1379). /apply now writes a drafted row to the tracker, a new drafted status flows through every reader honestly (unsent drafts are never counted as "waiting for a reply", never chased with follow-ups, and never reported as submitted), and an employer's acknowledgement email lets /gmail-sync promote the row to applied with the real date - catching exactly the case where you submitted by hand and forgot to run /outcome.
Live postings are no longer declared dead. WebFetch identifies itself as a bot, and many corporate sites answer it with HTTP 403 while serving browsers normally. /rank read that as "posting gone" and marked live jobs expired; /apply silently fell back to search-snippet prose (#277, reported and fixed by @kblackma). The new 09-web-research.md methodology file defines the trust boundary and a retry escalation - and the retry is gated on robots.txt via the new tools/robots_check.py: a site whose published policy permits access gets a browser-header retry past its WAF; a site that has said no is never overridden. The gate itself was then adversarially hardened (#286): a soft-200 HTML page no longer reads as permission, and percent-encoded Disallow rules now match.
Honest self-identification, completed
Every shipped portal CLI now sends an identifying User-Agent token instead of a browser spoof: jobnet and jobdanmark (#283 by @oscarbol09), jobbank and the jobdanmark detail path, and finally linkedin-search - the last holdout. Live-verified on every portal: identical responses under the honest token. Sites can now see exactly who is asking, which is the posture the robots gate depends on.
Also in this release
- Danish demo portals ship disabled (#288, suggested by @a-vysokov) -
/setupenables them when your market is Denmark; everyone else's/scrapestops spending tokens on Danish boards it never needed. --jobage-minuteson linkedin-search (#302 by @kgurnoor) - sub-day freshness windows for early-applicant timing; LinkedIn filters at second granularity server-side..envcan no longer be committed (#303 by @Haseeb-1698) - ignored and pinned in the security guard, protecting forks whose generated portal skills use an API token.- Negative and fractional filter flags rejected in the Danish portal CLIs (#281 by @oscarbol09) - some portals silently ignore invalid filters and return unfiltered results.
- The upstream checker no longer prints a false all-clear when upstream renames or deletes a tracked file (#282 by @oscarbol09), and it now tracks
09-web-research.md. - Spec-pinning tests for the Language Gate's
/rankcontract (#278 by @GabrielMensi). - A video walkthrough in the README's Quick start - The Next New Thing's hands-on tour of the workflow, for newcomers who want to see it before reading.
Fork reconciliation
python3 tools/check_upstream_updates.py will flag three methodology files this release:
| File | Version | What changed |
|---|---|---|
job-application-assistant/SKILL.md |
1.1.0 → 1.3.0 | Bash added to allowed-tools (robots-gated retry); new Step 3b records the application |
03-writing-style.md |
1.1.0 → 1.2.0 | Rule 5 routes 403s through the web-research escalation |
09-web-research.md |
new, 1.1.0 | New file - adopt as-is unless you have your own fetch conventions |
Two personalized-file landmines to check by hand:
- Danish-market forks: the four Danish portal
SKILL.mdfiles now shipenabled: false. If you search the Danish market, flip them back toenabled: trueafter updating (or re-run/setup --section search). Forks that already curated their portal set are unaffected. - Tracker vocabulary:
draftedis now a legal tracker status. If your fork customized/outcome,/gmail-sync,/html-report, or/notion-sync, reconcile the new status handling - unsent drafts must stay out of funnel statistics and follow-up nudges. (A full single-definition cleanup of the status enum is in review as #304's sibling effort, #298/#299 - it will land in a future release.)
Credits
@jakob1379 (the #269 diagnosis and the #291 fix, plus the #298 follow-up analysis), @kblackma (the 403 investigation, robots_check.py, and the adversarial hardening in #286), @oscarbol09 (#281, #282, #283), @GabrielMensi (#278), @kgurnoor (#302), @Haseeb-1698 (#303), @a-vysokov (#288).
v1.3.0
2026年08月04日
Why update
Your CV's dates may be invisible to ATS parsers. The stock template wrote date ranges as [YYYY--YYYY], and LaTeX turns -- into an en-dash glyph that extracts from the PDF text layer as � - so an ATS reads 2016�2024 and parses no date range at all. This failed silently: the rendered page looks perfect and every existing check passed. Confirmed against a real Workday import (a lost end date and zero education entries). The template now uses ASCII hyphens, and 05-cv-templates.md documents the check that catches it. If you built CVs from this template, see the CHANGELOG's fork reconciliation note for a one-line grep that finds affected files. (#276, thanks @kblackma)
The Language Gate. The framework never checked a posting's language requirements against what you actually speak. Now it does, everywhere: /setup captures a structured Languages table (asked directly, or read from your CV/LinkedIn export), and a posting requiring a language you haven't declared is excluded before scoring, while one asking for a higher level than you declared is flagged for your own judgment - never silently dropped, never silently passed. Wired through /scrape, /rank (persisted to seen_jobs.json), and /apply. (#275, thanks @GabrielMensi)
For fork maintainers - read before pulling
This is the heaviest reconciliation release so far. Three methodology files bumped framework_version (01-candidate-profile.md 1.0.0 → 1.1.1, 04-job-evaluation.md 1.1.0 → 1.2.2, 05-cv-templates.md 1.3.0 → 1.4.0), and two changes land on lines your fork has personalized:
- CLAUDE.md / 01-candidate-profile.md: the free-form
Languages:line becomes a structured table. Expect a conflict there; keep your languages, adopt the table shape. - cv/main_example.tex: the five
\cventrydate fields changed--to-. Keep your own dates, apply the same one-character change, and run the grep from the CHANGELOG over your other CV variants.
Run python3 tools/check_upstream_updates.py after fetching - it lists exactly which of your personalized files this release touched, with per-file diff commands.
Also in this release
/upskillaggregate mode consumes the gaps/rankrecords instead of re-inferring them, completing the #258 pair (#264, thanks @NotAbdelrahmanelsayed)tools/convert_salary_excel.pyparses localized numeric text cells ("108,5","1.234,5") instead of silently dropping them, and deliberately skips the one ambiguous pattern ("1,234") rather than guessing (#272, thanks @ayobamiseun)tools/check_upstream_updates.pyno longer fires a spurious fork-vs-self warning on lowercased clone URLs (#273), and SETUP.md section 8 now shows thegit remote add upstreamcommand it always assumed you'd run (#274) - both thanks @oscarbol09- The gitignore guard now enforces every personal-output ignore rule, so any future weakening of the personal-data boundary fails CI (#271, thanks @ayobamiseun)
Full details in the CHANGELOG.
详细ChangeLogv1.2.0
2026年08月02日
Why update
A privacy correction leads this release. SETUP.md used to say your fork is "private working space" - it is not: forks of public GitHub repositories are always public and cannot be made private, so that sentence pointed people toward committing personal profile data somewhere anyone could read it. Section 8 now states the fork-is-public fact plainly and documents the safe setup (a private repository with this repo as the upstream remote), and /setup now ends with a privacy note at the exact moment your profile data first lands in git-tracked files. If you currently push personalization to a public fork, read the updated SETUP.md section 8 before your next push. (Thanks @rasstamann, discussion #266.)
Pairing with it: tools/check_upstream_updates.py no longer reports a false "up to date with upstream" when a fork without an upstream remote silently falls back to comparing against itself - the default state of a plain fork clone. It now tells you the comparison is fork-vs-self and shows the exact git remote add upstream command. In other words, the update checker this release note tells you to run actually works in a fresh fork now. (Thanks @oscarbol09, #265.)
Also in this release
/rankpersists its per-jobstrengthsandgapsintoseen_jobs.jsoninstead of discarding them after the terminal output, replaced (never accumulated) on--allre-ranks - so downstream consumers can read real triage findings. First half of the #258 proposal by @NotAbdelrahmanelsayed; the/upskillconsumer side is in review as #264.- Two more personal-data gitignore rules are now CI-guarded (
cover_letters/Cover_*.*,cv/*.txt) - the rules already existed; now weakening them fails CI. (Found via a review-bot triage on @jakob1379's fork.) - CONTRIBUTING's "run what CI runs" list is complete (it omitted
security_guards.pyand the exactunittestcommand - thanks @jakob1379, #262), the vestigialcover_letters/OpenFonts/cover.clsis gone, and #252's ragged-row fix gained regression tests.
For fork maintainers
No framework_version bumps in this span - none of the personalized methodology files (01-08, the assistant SKILL.md, AGENTS.md) changed, so git merge v1.2.0 should land without conflicts in profile-bearing files. Full details in CHANGELOG.md.
v1.1.0
2026年07月30日
The first feature release since the 1.0.0 baseline - 16 commits, 8 contributors' work, and one fix worth updating for on its own.
Why update
Privacy fix (#238): if you registered a custom Typst (or other non-LaTeX) template, your personalized cv/main_<company>.typ files were not covered by the personal-data gitignore rules and would be committed to a public fork. The rules now cover cv/main_*.* and cover_letters/cover_*.* with the *_example.tex files still tracked. After updating, run git status once to confirm nothing personalized is staged.
Highlights
- Typst (and any compile-to-PDF toolchain) via
/add-template- templates declare their own compile command; the stock LaTeX templates are unchanged (#238) - freehire-search returns full descriptions in one request - no more per-hit detail fetches during
/scrape(#251) - Confirmed facts write back to your profile -
/applyand/interviewno longer lose facts you confirm mid-conversation (#211) - Optional application-form prep sheet as a third
/applyartifact for postings with screening questions - opt-in, the default two-document output is unchanged (#212) - Dependency review is live - CI now blocks PRs introducing dependencies with known high-severity vulnerabilities, on this repo and (self-activating) on forks (#254)
- The extension model, documented - README now spells out the three extension points and how to safely borrow a portal skill from another fork (prompted by #249)
Plus honesty rules for in-progress qualifications in the CV methodology (#210), mass-posting flags in /scrape (#207), pinned retry contracts across all portal CLIs (#246), and a batch of fixes - full details in CHANGELOG.md.
Updating a personalized fork
Follow SETUP.md section 8 - python3 tools/check_upstream_updates.py previews which of your personalized files this release touches before you merge. framework_version bumps in this release: 05-cv-templates.md 1.2.1 -> 1.3.0.
v1.0.0
2026年07月22日
First tagged release. This marks the framework as stable and gives forks a described checkpoint to update against instead of a moving master. It is a baseline of what already exists rather than a set of new changes; subsequent releases will document what changed since the previous tag.
Updating your fork: prefer this release over pulling raw master. git fetch upstream --tags then merge the tag. python3 tools/check_upstream_updates.py previews which of your personalized files an update touches. See SETUP.md §8.
At this baseline the framework provides:
- Application workflow - a drafter/reviewer
/applypipeline (CV + cover letter), plus/setup,/scrape,/rank,/interview,/outcome,/upskill,/expand,/html-report,/gmail-sync,/notion-sync,/add-portal,/add-template, and/reset. - Portal search skills - country-agnostic job-board CLIs (LinkedIn, freehire, and the Danish boards) in the portable Agent Skills format under
.agents/skills/, discovered and orchestrated by/scrape, with anenabled:toggle. - Framework versioning -
framework_versionmarkers plustools/check_framework_version.py(CI guard) andtools/check_upstream_updates.py(fork-side update preview). - Privacy and safety guards -
.gitignoreprotection for personal data, thetools/security_guards.pyallowlist, and a CI policy of making no live portal requests. - Cross-runtime support - a root
AGENTS.mdpointer so Codex and Antigravity can discover the portable portal skills, with Claude Code as the reference runtime.
Full details in CHANGELOG.md.
详细ChangeLog