mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
Every PR to this repo is currently verified by hand. This adds the checks
a machine can do:
- latex-smoke: compiles cv/main_example.tex (lualatex) and the new
cover_letters/cover_example.tex (xelatex) in the texlive/texlive
container, failing on any LaTeX error. Exact page-count assertions
(CV=2, cover letter=1) run on the upstream repo only
- lint (tools/lint_skills.py, also runnable locally): every SKILL.md has
parseable YAML frontmatter with name+description (frontmatter breakage
happened before - 37a0eed), allowed-tools 'bun run <path>' targets
exist, command files start with a '# /<name>' title, settings.json is
valid JSON with a permissions.allow list
- cli-typecheck: bun install + tsc --noEmit for all five portal CLIs
(matrix, fail-fast off)
- placeholder-integrity (upstream only): tracked template files still
carry their placeholder tokens, catching accidental personal-data
commits before they land
Fork-friendly by design: /setup personalizes CLAUDE.md, the skill files,
and main_example.tex in forks, so placeholder checks and exact page
counts are guarded with github.repository == upstream; compile success
and lint run everywhere. Live CLI smoke tests are deliberately excluded:
network-flaky, and linkedin-search is personal-use-only per its own ToS
warning - CI-automated requests would violate it. CLIs are typechecked
instead.
The cover letter previously had no tracked example (cover_*.tex is
gitignored), so cover_example.tex is new: a placeholder letter following
the documented 06 structure, demonstrating the correct itemize-outside-
lettercontent pattern. It doubles as the structural reference /apply
Step 2 looks for on fresh clones, which until now matched nothing. The
gitignore exception is ordered after Cover_*.tex because case-insensitive
filesystems match that pattern against cover_example.tex too.
Writing it surfaced a latent bug in the documented template itself:
06-cover-letter-templates.md's structure ends with \closing{Kind
regards,\} - but cover.cls appends its own \, and the doubled break
produces '! LaTeX Error: There's no line here to end.' on every compile
(nonstopmode swallows it, so it went unnoticed). Fixed in 06 and noted
in the example.
172 lines
6.3 KiB
Markdown
172 lines
6.3 KiB
Markdown
# Cover Letter Templates and Tailoring Guide
|
|
|
|
## Template: Custom cover.cls (XeLaTeX)
|
|
|
|
Cover letters use a custom LaTeX document class (`cover.cls`) with Lato/Raleway fonts.
|
|
|
|
**Output file:** `cover_letters/cover_<company>_<role>.tex`
|
|
**Compile with:** XeLaTeX (cover.cls requires fontspec)
|
|
**Font directory:** `cover_letters/OpenFonts/fonts/`
|
|
|
|
### Compile command
|
|
|
|
```bash
|
|
cd cover_letters && xelatex -interaction=nonstopmode cover_<company>_<role>.tex
|
|
```
|
|
|
|
Expected output: `Output written on cover_<company>_<role>.pdf (1 page, ...)`. Any page count other than 1 is a failure that must be fixed before presenting to the user.
|
|
|
|
## Compile-and-Inspect Loop (MANDATORY)
|
|
|
|
After writing the cover letter and before presenting to the user, always compile and visually inspect the PDF. Iterate until the layout is clean:
|
|
|
|
1. Run `xelatex -interaction=nonstopmode cover_<company>_<role>.tex`
|
|
2. Confirm page count is exactly 1 and compile succeeded
|
|
3. Read the PDF via the Read tool and visually check: signature fits at the bottom, no text cut off, bullet font matches body
|
|
|
|
### Known template pitfall: itemize inside `\lettercontent{}`
|
|
|
|
The `\lettercontent{}` macro appends `\\` to its argument. This breaks when the argument ends in `\end{itemize}` because `\\` has no line to break after the environment closes, producing `! LaTeX Error: There's no line here to end.` and no PDF output.
|
|
|
|
**Wrong (breaks compile):**
|
|
```latex
|
|
\lettercontent{Here is how my experience maps:
|
|
\begin{itemize}
|
|
\item ...
|
|
\end{itemize}}
|
|
```
|
|
|
|
**Correct — close `\lettercontent{}` before the list and wrap the list in the matching Raleway-Medium font so typography stays consistent:**
|
|
```latex
|
|
\lettercontent{Here is how my experience maps:}
|
|
|
|
{\raggedright\fontspec[Path = OpenFonts/fonts/raleway/]{Raleway-Medium}\fontsize{11pt}{13pt}\selectfont
|
|
\begin{itemize}
|
|
\item ...
|
|
\end{itemize}\par}
|
|
\vspace{6pt}
|
|
|
|
\lettercontent{[next paragraph]}
|
|
```
|
|
|
|
The font wrapper is mandatory — if you just move `\begin{itemize}` outside `\lettercontent{}` without the `\fontspec` block, bullets render in the default body font (Lato) and visually mismatch the rest of the letter.
|
|
|
|
## Document Structure
|
|
|
|
```latex
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Cover Letter - [Company], [Role]
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
|
\documentclass[]{cover}
|
|
\usepackage{fancyhdr}
|
|
|
|
\pagestyle{fancy}
|
|
\fancyhf{}
|
|
|
|
\rfoot{Page \thepage \hspace{0pt}}
|
|
\thispagestyle{empty}
|
|
\renewcommand{\headrulewidth}{0pt}
|
|
\begin{document}
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% TITLE NAME
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
\namesection{}{\Huge{[YOUR_NAME]}}{ \href{mailto:[YOUR_EMAIL]}{[YOUR_EMAIL]} | [YOUR_PHONE] | \urlstyle{same}\href{[YOUR_LINKEDIN_URL]}{LinkedIn}
|
|
}
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% MAIN COVER LETTER CONTENT
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
|
\currentdate{\today}
|
|
\lettercontent{Dear [Name/Team],}
|
|
|
|
\lettercontent{[Opening paragraph - role, connection to background, 2-3 sentences]}
|
|
|
|
\lettercontent{[Body paragraph - most relevant experience, then bullet list]
|
|
|
|
\begin{itemize}
|
|
\item [Concrete achievement/skill 1]
|
|
\item [Concrete achievement/skill 2]
|
|
\item [Concrete achievement/skill 3]
|
|
\end{itemize}
|
|
|
|
[Connection to company - why this role, why this company specifically]}
|
|
|
|
\lettercontent{[Personal fit paragraph - behavioral strengths, team contribution, 2-3 sentences]}
|
|
|
|
\lettercontent{I look forward to hearing from you.}
|
|
|
|
\begin{flushright}
|
|
% No trailing \\ inside \closing{} - cover.cls appends its own \\, and a
|
|
% doubled break triggers "! LaTeX Error: There's no line here to end."
|
|
\closing{Kind regards,}
|
|
|
|
\signature{[YOUR_NAME]}
|
|
\end{flushright}
|
|
\end{document}
|
|
```
|
|
|
|
## Key Commands Reference
|
|
|
|
| Command | Purpose |
|
|
|---------|---------|
|
|
| `\namesection{}{Name}{contact info}` | Header with name and contact |
|
|
| `\currentdate{date}` | Date field (use `\today` or explicit date) |
|
|
| `\lettercontent{text}` | Body paragraph (adds spacing after) |
|
|
| `\closing{text}` | Closing line |
|
|
| `\signature{name}` | Printed name below signature |
|
|
|
|
## Tailoring Guidelines
|
|
|
|
### Salutation
|
|
- If you know the hiring manager's name: "Dear [First Last],"
|
|
- If you know the team: "Dear [Company] hiring team,"
|
|
- Generic: "Dear [Company]," (avoid "To whom it may concern")
|
|
|
|
### Length - Hard 1-Page Limit
|
|
- Target: 1 page including signature block
|
|
- Maximum: **never exceed 1 page**
|
|
- **Word budget: 250-300 words** of body text (not counting LaTeX markup). This is the safe maximum. 350 words will overflow.
|
|
- **Always count**: opening paragraph + bullet list paragraph + closing paragraph = 3 blocks. Add a 4th only if the others are short.
|
|
- When adding company-specific content, trim other content to compensate rather than adding net length
|
|
|
|
### Line Spacing
|
|
- Add `\usepackage{setspace}` and `\setstretch{1.0}` if the letter is long and needs to fit on one page
|
|
- Use `\vspace{.5cm}` between major sections for readability (only if space permits)
|
|
|
|
### Bullet Lists
|
|
- Place `\begin{itemize}...\end{itemize}` **outside** a `\lettercontent{}` block (see "Known template pitfall" above), wrapped in the matching Raleway-Medium `\fontspec` so the bullet font matches the body
|
|
- 3-5 bullets is ideal
|
|
- Start each bullet with bold label or action verb
|
|
- Use `\textbf{Label:}` for category-style bullets
|
|
|
|
### LaTeX Special Characters
|
|
- Underscore: `\_`
|
|
- Ampersand: `\&`
|
|
|
|
### Non-English Cover Letters
|
|
- Same template structure, just write content in the posting's language
|
|
- Adjust date format to local convention
|
|
- Adjust closing to local convention (e.g. "Med venlig hilsen," for Danish)
|
|
|
|
## Checklist Before Finalizing
|
|
- [ ] No em-dashes (use commas or periods instead)
|
|
- [ ] No cliches or empty filler
|
|
- [ ] Every claim backed by specific example
|
|
- [ ] Forward-looking framing: focuses on tasks you'll solve, not just past duties
|
|
- [ ] Motivation section references this specific company's mission/values
|
|
- [ ] Company name and role are correct throughout
|
|
- [ ] Date is current
|
|
- [ ] Fits on one page
|
|
- [ ] Language matches the job posting language
|
|
- [ ] Salutation is appropriate (named person if possible)
|
|
- [ ] Headline is engaging and specific, not generic
|
|
|
|
## Submission Guidelines (Best Practice)
|
|
- Submit only the documents the employer requests
|
|
- Export as PDF to preserve formatting
|
|
- Name files clearly: "[Your Name] CV" and "[Your Name] Cover Letter"
|
|
- Follow all employer instructions regarding anonymity or specific materials
|