MadsLorentzen/ai-job-search
73.9
Strong · 18 September 2026
2.6k
lines of production code
Python
primary language
1
measurement over time
What this system is
This system is a command-line framework for automating job applications, featuring modular CLI tools to search and retrieve job listings from multiple portals including LinkedIn, Jobbank, Jobindex, Jobnet, and Freehire. It manages the end-to-end application workflow by ingesting user documents to generate tailored CVs and cover letters in LaTeX, while tracking application status and outcomes. The architecture emphasizes robustness through strict input validation, network resilience with timeouts and retries, and comprehensive security measures for personal data isolation and URL verification.
Features
Add job detail and search CLI commands for Jobbank
The CLI now includes a \detail\ command to retrieve full information for a single job posting by ID or URL, parsing JSON-LD data to display fields like title, company, location, deadline, and employment type in JSON or plain text formats. Additionally, a \search\ command allows users to query job listings via RSS feed using various filters (keywords, job type, location, etc.), with support for multiple output formats (JSON, table, plain) and client-side result limiting. The search implementation also handles unparseable dates gracefully to prevent crashes.
.agents/skills/jobbank-search/cli/src/commands · high confidence
Documents folder structure and ingestion documentation
Added the \documents/\ folder structure and its \README.md\ to define how the \/setup\ command ingests career materials. The documentation specifies supported subfolders (\cv/\, \linkedin/\, \diplomas/\, \references/\, \projects/\, \postings/\) and their accepted file formats, explaining how \/setup\ extracts skills, experience, and project details to populate candidate profile files. It also details the \applications/\ folder structure for tracking past applications, including the naming convention for subfolders (\\<company\>\_\<role\>\) and the expected files (\job\_posting.md\, \cover\_letter.tex\, \cv\_draft.tex\, \outcome.md\) used for calibration and history.
documents · high confidence
New LinkedIn Search and Detail CLI commands
The CLI now includes dedicated \search\ and \detail\ commands for interacting with LinkedIn job data. The \search\ command allows users to query jobs by keyword, location, and work type (remote/hybrid/onsite), with support for sub-day freshness filtering via \--jobage-minutes\, pagination, result limiting, and output in JSON, table, or plain formats. The \detail\ command fetches full information for a specific job using a job ID, URL, or URN, and reports the posting status as ACTIVE or CLOSED/EXPIRED, also supporting JSON, table, and plain text outputs.
.agents/skills/linkedin-search/cli/src/commands · high confidence
New tooling for framework versioning, upstream updates, and job deduplication
The tools directory now includes several new scripts to improve framework maintenance and job scraping reliability. \check\_framework\_version.py\ enforces that modified framework files under \.claude/skills/job-application-assistant/\ (including \AGENTS.md\) have updated \framework\_version\ frontmatter markers. \check\_upstream\_updates.py\ compares local framework file versions against the upstream \MadsLorentzen/ai-job-search\ repository, with case-insensitive URL matching and warnings when the remote does not point to the template repo. \job\_key.py\ provides a canonical, deterministic deduplication key for \seen\_jobs.json\ entries, fixing issues with non-ASCII characters, path-breaking punctuation, and duplicate entries from inconsistent truncation. \lint\_skills.py\ validates skill frontmatter, command file titles, and settings JSON structure. \rank\_state.py\ optimizes the \/rank\ workflow by moving state file read/write operations out of the model's context, using atomic writes and efficient selection/sweep/apply subcommands. \robots\_check.py\ implements RFC 9309-compliant robots.txt parsing to safely determine if browser-header retries are permitted. \security\_guards.py\ enforces allowlists for \.claude/settings.json\ permissions and hooks, \.gitignore\ personal-data rules, and package.json lifecycle scripts. \upstream\_triage.py\ generates a Markdown report of upstream commits not yet applied to the fork, distinguishing between relevant changes and those already applied or irrelevant to the fork's scope. \verify\_layout.py\ measures compiled PDF layout properties using Poppler bounding boxes to detect orphaned entries, internal holes, and other formatting issues.
tools · high confidence
Placeholder added for company research module
A .gitkeep file was added to the company\_research directory to ensure the folder is tracked by version control, establishing the location for future company research functionality.
_company\research · low confidence
Security
Framework-wide security hardening and personal data isolation
The framework now treats job postings as untrusted input, enforcing strict source host verification in /apply to reject spoofed URLs and preventing the agent from following links embedded in posting text. Personal data is rigorously isolated: the .gitignore has been expanded to cover all generated application outputs (CVs, cover letters, interview prep), tracker files, and upskill reports at any directory depth, and a new .env ignore rule prevents accidental commit of portal API tokens. A new SECURITY.md documents these threat-model defenses, including the permission allowlist and personal-data boundaries.
(repo-wide) · high confidence
Behavioural changes
Refactored LaTeX class and added cover letter template
The cover letter generation system has been updated with a new class file (cover.cls) that renames the underlying LaTeX class from 'deedy-resume-openfont' to 'cover' and removes redundant package imports (such as fontspec and titlesec) to streamline compilation. A new example template (cover\_example.tex) has been added to demonstrate the correct structure for generating a one-page cover letter, including specific guidance on itemize placement and font usage. Additionally, the old OpenFonts/cover.cls file has been removed to prevent conflicts.
_cover\letters · high confidence
freehire-search CLI: stricter validation, robust error handling, and new search options
The freehire-search CLI now rejects unknown flags with a clear error instead of silently discarding them, preventing incorrect search results from malformed input. Numeric flags like --jobage, --page, and --limit strictly require whole numbers (≥1), rejecting fractional or zero values that previously caused silent filter drops. The tool also introduces a --no-description flag for faster, cheaper discovery passes by skipping full description hydration, and supports --description-format to control how descriptions are rendered. Additionally, all API requests now include a 15-second timeout to prevent hanging on stalled connections, and the CLI gracefully handles transient server errors (429/5xx) with exponential backoff before failing fast on connection issues.
.agents/skills/freehire-search/cli · high confidence
Fixes
CLI rejects unknown flags and enforces request timeouts
The Jobnet CLI now strictly rejects undefined command-line flags (both long and short forms) instead of silently ignoring them, preventing incorrect search results caused by mistyped filters. Additionally, all API requests now include a 15-second timeout to prevent hanging connections, and the CLI accepts full posting URLs in detail commands by normalizing job IDs from various URL formats.
.agents/skills/jobnet-search/cli/src · high confidence
CLI robustness and data parsing improvements
The CLI now rejects unknown flags (both long and short forms) with a clear error instead of silently discarding them, preventing incorrect search results. It also enforces a 15-second timeout on all network requests to avoid hanging. Additionally, the tool better handles Jobindex's client-side rendering by extracting data from the HTML 'Stash' blob, correctly maps 'ASAP' deadlines to null, and fixes HTML entity decoding to support hexadecimal codes.
.agents/skills/jobindex-search/cli/src · high confidence
CLI robustness: unknown flag rejection, Cloudflare error handling, and JSON-LD parsing
The Jobbank CLI now rejects undefined flags (both long and short forms) with a clear JSON error instead of silently discarding them, preventing incorrect search results. It also provides a specific error message when blocked by Cloudflare bot protection and improves detail command reliability by parsing JobPosting entries nested within JSON-LD @graph structures. Additionally, deadline dates in search output are normalized to YYYY-MM-DD format, and a 15-second timeout is applied to all fetch requests.
.agents/skills/jobbank-search/cli/src · high confidence
CLI robustness: unknown flag rejection, request timeouts, and retry logic
The Jobdanmark CLI now rejects undefined flags (both long and short forms) with a clear JSON error instead of silently discarding them, preventing accidental unfiltered searches. All API and HTML fetches include a 15-second timeout and a consistent User-Agent header. The detail command's HTML fetching now uses the same retry/backoff logic as the JSON API, ensuring it handles rate limits (429) and server errors (5xx) gracefully rather than failing immediately. A helper function was added to normalize job posting URLs for the detail command.
.agents/skills/jobdanmark-search/cli/src · high confidence
Fix CV template compilation and rendering issues on moderncv
The main CV template (main\_example.tex) has been updated to resolve compatibility issues with various moderncv versions and LaTeX engines. It now recommends lualatex as the primary compiler to avoid font-expansion errors, while adding T1 font encoding for pdflatex to ensure correct text extraction by ATS systems. The banking style colors are now correctly applied to names and section headings across different moderncv versions (including Debian/Ubuntu apt packages). Additionally, the template removes unnecessary vertical spacing between list items, uses ASCII hyphens for date ranges to prevent parsing issues, and wraps list items in braces to ensure consistent bullet rendering.
cv · high confidence
Fixes to Jobindex.dk CLI detail and search commands
The CLI now correctly handles Jobindex.dk detail pages by rejecting non-Jobindex URLs to prevent fetching irrelevant content, decoding HTML entities (including Danish characters and numeric references) in output, and parsing the updated page structure for native and external ATS job listings. The search command enforces strict validation on pagination and filter flags, rejecting negative or fractional values to ensure reliable query execution.
.agents/skills/jobindex-search/cli/src/commands · high confidence
LinkedIn Search CLI: robust flag validation, sub-day freshness, and reliable HTML parsing
The new LinkedIn Search CLI introduces stricter input validation and improved data handling for job searches. It now rejects unknown flags with a clear error instead of silently ignoring them, preventing incorrect search results. Users can filter by sub-day freshness using the new --jobage-minutes flag, which works alongside the existing --jobage days filter. The CLI also validates that numeric flags like --page and --limit are whole numbers, rejecting fractional inputs that previously caused silent errors. Additionally, the tool correctly decodes HTML entities in output and extracts nested job descriptions without truncation, ensuring accurate and complete job details are displayed.
.agents/skills/linkedin-search/cli/src · high confidence
Restore and fix Danish portal CLI commands
Restores the Danish portal CLI commands (autocomplete, categories, detail, locations, search) that were previously dropped, and applies several fixes to their behavior: the search output now drops presentation-only keys (like cover images) to reduce payload size while keeping contract fields, the detail command now accepts full posting URLs and includes an HTML fallback for parsing, the autocomplete command filters out items with null text to prevent crashes, and input validation now rejects negative or fractional values for pagination and filter flags.
.agents/skills/jobdanmark-search/cli/src/commands · high confidence
Restore and harden Danish job portal CLI commands
Restores the Danish portal CLI commands (detail, search, occupations, suggestions) that were previously dropped by .gitignore, and adds robustness to their behavior. The detail command now handles external job ads that return 404 from the detail endpoint by falling back to the search endpoint, and maps the API's 1900-01-01 deadline sentinel to null so undisclosed deadlines do not appear expired. The search command prevents crashes from null publication dates by enforcing nullable typing and guards, and ensures the output contract includes all expected fields. Input validation rejects negative or fractional values for pagination and filter flags, and accepts full posting URLs in detail commands.
.agents/skills/jobnet-search/cli/src/commands · high confidence
Test coverage
Added comprehensive test coverage for the Jobindex CLI; Added tests for /apply source host verification and page-count enforcement; Test coverage for LinkedIn Search CLI validation, parsing, and network behavior.
Dependencies
Pin internal dependencies and add new job-search CLI packages
The jobbank-search, jobdanmark-search, jobindex-search, and jobnet-search CLI packages now pin @bunli/core to 0.9.1, @bunli/utils to 0.6.0, and @types/bun to 1.3.14, replacing previous 'latest' references to prevent CI type-drift. Additionally, new country-agnostic CLI packages for freehire-search and linkedin-search have been added, each using TypeScript 5.4.0 and @types/bun 1.3.14 as dev dependencies with no runtime dependencies.
(dependencies) · high confidence
Housekeeping
Placeholder directory created for upskill feature
An empty directory structure has been initialized for the upskill feature, indicated by the addition of a .gitkeep file. This serves as a placeholder for future implementation of the /setup\_docs, /reset, /expand commands and /upskill skill mentioned in the commit message, but no functional code or user-facing changes are present in this diff.
upskill · high confidence
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
How this codebase got here
Baseline
- First survey — no prior run to compare against. CAI 74.
Lenses
- Code Health 82
- Architecture 100
- Maturity 65
- Readiness 77
- Security 86
Changes since last survey
- 283 commits — 165 feature/other, 118 fixes
By area
- (root) — 144 commits
- .agents/skills — 57 commits
- .claude/commands — 28 commits
- .claude/skills — 18 commits
- tests/test_convert_salary_excel.py — 7 commits
- .github/workflows — 5 commits
- tests/test_check_upstream_updates.py — 4 commits
- (repo) — 3 commits
- tests/test_security_guards.py — 3 commits
- .github/FUNDING.yml — 2 commits
- assets/mascot — 2 commits
- .claude/settings.json — 1 commit
- cover_letters/OpenFonts — 1 commit
- cv/main_example.tex — 1 commit
- documents/README.md — 1 commit
- tests/init.py — 1 commit
- tests/test_lint_skills.py — 1 commit
- tests/test_rank_command.py — 1 commit
- tests/test_salary_lookup.py — 1 commit
- tests/test_scrape_contract.py — 1 commit
Notable commits
- fix: Fix .gitignore: stop the blanket .agents/ rule dropping scraper skills (#21)
- fix: Fix salary tool edge cases (#75)
- fix: Merge pull request #347 from MadsLorentzen/fix/2026-08-19-review-fixes
- fix: This PR fixes two genuine gaps in the framework — a documented command with no backing file, and CLI tools that were installed but never called. (#52)
- fix: chore: untrack tracker CSV, scope scraper Bash permission, fix portal SKILL.md paths (#71)
- fix: fix(apply): archive the job posting while /apply still holds it (#306) (#307)
- fix: fix(apply): name CVs main_<company>_<role> to avoid overwrites (#171)
- fix: fix(apply): record the drafted application in the tracker (#269) (#291)
- fix: fix(apply): restore QC coverage dropped during earlier token-efficiency refactor
- fix: fix(apply): run the page-count check Step 5b claimed Step 5d already ran (#476)
- fix: fix(archive): derive <company>_<role> as a single path component (#329)
- fix: fix(cache): address PR #349 follow-up feedback (#359)
- fix: fix(check_upstream_updates): compare template repo URL case-insensitively (#273)
- fix: fix(check_upstream_updates): warn when check falls back to a fork's own origin (#265)
- fix: fix(ci): co-locate placeholder sentinels with the data they guard
- fix: fix(ci): skip placeholder-integrity tests on forks in python-tests (#407)
- fix: fix(ci): skip the pristine-template guard in test_setup_command on forks (#463)
- fix: fix(cli): catch unhandled promise rejections in self-contained CLIs (#203)
- fix: fix(cli): honest User-Agent token on linkedin-search
- fix: fix(cli): honest User-Agent tokens on jobbank and jobdanmark detail
- …and 263 more
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
Survey your own repository
MadsLorentzen/ai-job-search was measured the same way every project in this corpus was: the same rubric, at a pinned commit, with the result published in full. Point a surveyor at a repository you know and see whether you agree with it.
About this page
- The score is its most recent published measurement, taken on 18 September 2026 at a pinned commit. It is not a live figure and does not change until the project is measured again.
- Measured at commit 09435eb1a572eddbd0180e8ea9c3acf84f90604a — the exact code this score is about.
- Scored under rubric-2026.09.15 — the same rubric and the same method as every other entry in this index.
- Measured by watchdog.canine.dev using codehealth-analyzer preprod-5d04157a340d.