Reviewer-side Factual Grounding Audit: every date, employer, job title, and quantitative metric in both drafts is checked against the union of 01-candidate-profile.md + cv/main_example.tex + CLAUDE.md's Candidate Profile section (grounded if ANY source supports it; inter-source mismatches surfaced as profile-consistency warnings; draft drift returned as Part A edits with reason "grounding"). Drafter-side rule makes those three sources the sole source of truth for facts - existing tailored CVs are structure/phrasing reference only. Fixes the structural drift loop where tailored output fed back as source material; companion to the maintainer-side #178 setup fix.
Reported, diagnosed, and implemented by @jovin-nicholas (#177); the failure mode was validated empirically by the output benchmark (blind judges found the exact escalation class in pre-audit outputs).
Closes#177
Seven improvements sourced from blind regression probes comparing current
outputs against real April-2026 baselines (each an area where the older
outputs scored better):
- every stated requirement addressed - matched or honestly gapped, never
silently omitted (the benchmark run omitted a stated Kubernetes
requirement entirely; omission reads as hiding under questioning)
- nice-to-haves engaged by name with honest adjacency framing; posting's
literal term preferred, including in CV section headings
- stated logistics/prerequisites addressed in the letter (clearances,
availability, job ID, multi-country language mapping)
- domain-transfer argument leads the CV profile statement for
domain-changers
- evidence links (href) on every verifiable named artifact
framework_version: 05 -> 1.2.0.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Both found by empirically re-running the /apply pipeline end-to-end:
06's Document Structure block still demonstrated itemize wrapped inside
lettercontent - the exact antipattern its own pitfall section forbids -
and following 05's needspace guidance at section level pushed an entire
Education block to a new page, costing a page instead of saving one.
framework_version: 05 -> 1.1.1, 06 -> 1.0.1.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The CV was hardcoded 'Always in English' - fine for the Danish/English
demonstration profile, a real disadvantage for fork users in markets
where applications are expected in the local language (cover letters
already match the posting's language). /setup now asks once and records
'CV language:' in CLAUDE.md's Identity section; /apply reads it with
English as the default, so existing users see zero behavior change.
The ATS keyword rule is reworded language-neutrally.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Completes the loop behind #177: /apply drift could be archived by
/outcome, then laundered into 05-cv-templates.md as a reusable template
by /setup Path A - promoting a one-off drifted claim into source
material for every future application. Path A now verifies extracted
statements' factual claims against 01-candidate-profile.md (keeping
framing only), and 05-cv-templates.md marks [Used for:] statements as
phrasing references, never fact sources. framework_version 1.1.0.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Prompt-injection hardening from the dataflow analysis in #173 by @Defaultuser361: data-not-instructions rules in /apply and /rank, reviewer research constrained to the user-confirmed company identity, writing-style verify rule tightened to independently located sources (framework_version 1.1.0), SECURITY.md private reporting channel, README note. Closes#173.
CVs from /apply were named cv/main_<company>.tex, so a second role at the same company overwrote the first (cover letters already carried the role). Aligns CV naming to main_<company>_<role>.tex across apply, add-template, the CV template guide, CLAUDE.md, and SETUP.md; /outcome and /interview fallbacks glob main_<company>*.tex to match both legacy and new names. framework_version bumped on both touched framework files.
Adds /notion-sync per the conditions agreed in discussion #166: tool-agnostic sync contract with Notion as the in-tree reference binding, silently optional (covers unconfigured, headless, and unauthenticated states), read-only toward the repo with the gitignored sync-state file as its only local write, write-once page bodies, documents sync as filenames only. Complements /html-report: deep local dashboard vs glanceable anywhere-view.
Self-contained HTML dashboard generated from job_search_tracker.csv and the application archives: stat cards, status/sector/channel/funnel charts as hand-generated inline SVG (no CDN, fully offline), HTML-escaped interpolation throughout, and a filterable applications table. Includes Python guards for the command file and the reports/ gitignore rule.
Implements the mechanism approved in discussion #93: enabled: true|false frontmatter on portal skills (missing key = enabled), honored during /scrape portal discovery, with skipped portals reported visibly in the run summary.
* Add framework_version markers to assistant skills and implement CI version guard and update checker
* Address review feedback: update ci.yml based on latest upstream, gate CI version guard to upstream repo, and remove non-ASCII characters from check_upstream_updates.py
* feat(brand): tagline under the H1 and the hired-moment coffee line
Two items from the branding strategy: the positioning line lands in the
README itself (the repo description already carries it), and /outcome's
hired path gets its single, value-framed donation ask - once per hire,
never nagging, never effort-framed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(brand): pay-it-forward framing for the hired-moment line
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Rebased onto current master. The SKILL.md description and "How It Works"
copy were already generalized by #102, so this drops those now-redundant
edits and keeps only the unique remaining value: replacing the hardcoded
Danish job boards (jobindex.dk, karriere.dk, jobfinder.dk, akademikernes.dk)
and Danish location terms in search-queries.md with configurable
placeholders and an /add-portal pointer, so the query templates apply to
any market.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to #65 feedback after #52 merged CLI-first search. Step 2 now
uses each portal's detail command for CLI-sourced jobs (WebFetch only for
WebSearch fallbacks). Skill description reflects market-agnostic portal
CLIs instead of Danish-only wording.
Co-authored-by: Cursor <cursoragent@cursor.com>
The docs instruct users to run /scrape, but the skill's name was
job-scraper, so /scrape never resolved as a command - it only worked
via fuzzy trigger matching on the description. Renaming the skill's
name field makes /scrape a real, autocompleted command, consistent
with /upskill (whose skill is named upskill). Folder path unchanged.
Fixes#68. No wrapper command per the single-source-of-truth
precedent (#52).
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- Untrack job_search_tracker.csv: it was both tracked and listed in
.gitignore (same inconsistency class as the settings.local.json fix
in #27). Users' personal rows risked merge conflicts on every pull;
commands already create the file with the standard header when it
is missing.
- Scope job-scraper's allowed-tools Bash entry (from #52) to
'bun --version' and the portal-CLI invocation pattern, adopting the
tighter form proposed in #65.
- Fix all five portal SKILL.mds documenting 'bun run skills/...'
paths that do not resolve from the repo root ('.agents/skills/...'
is correct) - now load-bearing since #52 wired /scrape to read
these docs for CLI invocations. Surfaced in #66.
- Teach tools/lint_skills.py to glob-expand allowed-tools bun run
targets so scoped wildcard permissions lint correctly.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
07-interview-prep.md holds STAR examples, tough-question patterns,
questions to ask, and a roleplay protocol - and the job-application-
assistant skill names interview preparation as its Step 4 - but no
command operationalizes any of it. /apply ends at 'files ready', and the
moment an interview is scheduled, nothing picks the thread back up.
/interview does, drawing on the archive /outcome now maintains.
/apply optimizes what the company reads; /interview optimizes what the
company hears. The bridge is consistency: the interviewer read the
submitted CV and cover letter, so prep must match what they claim.
How it works:
- Loads the application context from documents/applications/
<company>_<role>/: the exact posting, the submitted drafts, and
outcome.md's stage + recorded feedback (stage-N feedback is the
highest-priority input for stage-N+1 prep). Graceful fallbacks for
applications that predate /outcome, stating plainly what is missing
- Runs the Company Research Checklist that 04-job-evaluation.md defines
but nothing executes, plus interviewer-angle notes from public
professional info only. Every company claim is independently verified
before it enters the pack - same rule as cover-letter claims
- Builds a stage-specific prep pack: likely questions (earlier-stage
feedback first, then fit-evaluation gaps with honest bridge answers
per 07's you-don't-have-X pattern, then posting requirements, then
stage type), STAR mapping via 07's Use-for tags with new drafts
grounded strictly in profile facts, a consistency brief of the
submitted documents' probeable claims, customized tough questions,
4-6 questions to ask, logistics tips
- Saves the pack to documents/applications/<company>_<role>/
interview_prep_<stage>.md - one per stage, kept as history,
gitignored with the rest of the archive
- Offers a mock interview following 07's Roleplay Guidelines verbatim,
with feedback calibrated against the behavioral profile (same
voice-consistency rule the /apply reviewer applies)
- Never edits framework or profile files, except appending
user-approved STAR examples to 07 on explicit request
Integration: /outcome suggests /interview when an interview stage is
recorded; documents/README.md documents the interview_prep_<stage>.md
files (and that /setup ignores them); README commands list and file
tree.
* feat: add /upskill command file to wire the upskill skill into Claude Code
The upskill skill and its full SKILL.md workflow already existed in
.claude/skills/upskill/SKILL.md, but there was no corresponding command
file in .claude/commands/. Without it, running /upskill in Claude Code
had zero structured behaviour — Claude would improvise with no defined
steps, mode detection, or output format.
This commit adds .claude/commands/upskill.md as the thin orchestration
layer that was missing:
- Step 0: Parses \ to determine aggregate mode (no args,
analyses all jobs in job_search_tracker.csv) vs. targeted mode
(a URL is passed, analyses that single posting). Unrecognised input
triggers a clarifying prompt rather than silently misbehaving.
- Step 1: In aggregate mode, reads the tracker and exits early with a
helpful message if it is empty, so the user is never dropped into a
broken analysis with no data.
- Step 2: Delegates all analysis work to the existing upskill SKILL.md
(hard skill diff, LLM synthesis, heatmap, web-searched resources,
study order, report save). No analysis logic is duplicated here.
- Step 3: Presents a concise post-run summary — critical/high gaps,
total estimated study time, and next-step suggestions (/scrape,
/apply, review the saved report).
Design principle: the command is intentionally a thin driver. All
substantive logic lives in SKILL.md so it remains in one place and
is easy to update independently of the command shell.
* fix: wire CLI tools into /scrape as primary search mechanism
The repo ships five Bun CLI search tools under .agents/skills/:
- jobindex-search (Jobindex.dk — largest Danish board)
- jobbank-search (Akademikernes Jobbank — academic/professional)
- jobdanmark-search (Jobdanmark.dk — broad coverage)
- jobnet-search (Jobnet.dk — government portal)
- linkedin-search (LinkedIn public jobs-guest API — country-agnostic)
Before this fix, none of them were ever called during /scrape. The
job-scraper SKILL.md told Claude to run WebSearch for everything,
meaning the CLIs were installed and documented but sat in dead-code
limbo with no callers.
Changes to .claude/skills/job-scraper/SKILL.md:
1. Added Bash to allowed-tools so the bun CLI commands are permitted
by Claude Code's tool-permission system. Without this, any attempt
to shell out would be blocked regardless of the instruction text.
2. Replaced the single WebSearch-only Step 1 with a three-part search
strategy:
Step 1a — bun availability check
Runs \un --version\ first. If bun is not installed the skill
gracefully degrades to WebSearch for all portals (Step 1c) and
notes the fallback in the results output, rather than crashing.
Step 1b — CLI tools as primary mechanism
For each query term extracted from search-queries.md, runs all five
CLIs with \--jobage 14 --limit 20 --format json\. Flags are
consistent with each tool's documented contract so output is
predictable. Each CLI call is independent: a non-zero exit or empty
result on one portal does not abort searches on the others. Results
are collected and merged before deduplication.
Step 1c — WebSearch fallback
Used for portals without a CLI skill (karriere.dk, jobfinder.dk,
company career pages via site: searches) and as the universal
fallback when bun is unavailable. This preserves backwards
compatibility for users who have not installed bun yet.
The net effect: /scrape now actually uses the CLI infrastructure the
repo was built around. WebSearch remains available for portals outside
the shipped skill set and for users on environments without bun.
* fix: rework /scrape CLI wiring + drop /upskill command file
Two changes addressing maintainer feedback on PR #52.
--- /scrape: use portal SKILL.md as source of truth ---
The previous approach hardcoded per-portal bun invocations directly
in job-scraper/SKILL.md. This broke in practice:
- jobbank requires --key (not --query); --query is not a valid flag
- jobnet uses --search-string and region/occupation filters; passing
--query silently returns the full unfiltered job firehose
- --sort date and uniform --jobage 14 are not supported by all portals
The fix removes all hardcoded per-portal flag examples. Instead, Step
1b now instructs the agent to:
1. Discover installed portal skills via .agents/skills/*/SKILL.md
2. Read each portal's own SKILL.md for its documented CLI interface
3. Translate search-queries.md terms into that portal's flag format
4. Use each portal's supported recency and limit flags
This makes the scraper self-maintaining: new portals added via
/add-portal are automatically included without any changes to this
file, and the scraper can never drift from the CLIs again.
The bun availability check (Step 1a) and Bash in allowed-tools are
preserved - both are still needed. The WebSearch fallback (Step 1c)
is preserved and cleaned up to cover: portals without a CLI skill,
any portal whose CLI fails at runtime, and the bun-unavailable case.
--- /upskill: drop command file ---
.claude/commands/upskill.md is removed. /upskill is deliberately
skill-hosted: .claude/skills/upskill/SKILL.md is the backing file
and parses its own /upskill vs /upskill <URL> modes (same pattern
as /scrape, which also has no command file). The command file created
a second entry point that duplicated the skill's argument parsing,
violating the single-source-of-truth principle established in #44
and #49.
---------
Co-authored-by: rajpratham1 <your-email@example.com>
/setup Path A already mines documents/applications/<company>_<role>/
(job_posting.md, submitted drafts, outcome.md) to calibrate
04-job-evaluation.md and surface STAR candidates - but nothing in the
workflow systematically writes those folders, so the calibration machinery
only runs for users who hand-maintain the archive. /outcome closes the
loop: it writes the data /setup reads.
How it works:
- Identifies the application from job_search_tracker.csv (by argument, or
by listing open applications); applications made outside the workflow
get a new tracker row
- Records progress updates (interview stages, offers) and resolutions
using the exact status enum documents/README.md documents, plus one
additive value: in_progress, for open applications between updates.
/setup's calibration only draws conclusions from final statuses
- Archives the submitted cv_draft.tex / cover_letter.tex (copy, never
move; existing archived files are never overwritten - the archive is
what was actually submitted) and fetches job_posting.md from the
tracker's source URL while it is still alive; a dead URL gets a
user-pasted copy or an explicit unavailable stub, never a
reconstruction
- Updates the tracker row's status and notes; never restructures the CSV
- After 3+ resolved outcomes (or a repeating pattern), points the user
back to /setup Path A - /outcome writes data, /setup interprets it,
and this command never edits framework or profile files itself
- Idempotent: re-running appends stages and dated notes, never
duplicates folders, rows, or history
Also aligns the outcome.md status enum across docs: setup.md Step A3
listed hired/rejected/no_response/interview_only while documents/README.md
already had offer_declined; both now carry the full enum including
in_progress. documents/applications/** and the tracker are already
gitignored, so all recorded data stays personal.
Docs: README (commands list, file tree), documents/README.md (/outcome
cross-reference and in_progress semantics), one-line handoff at the end
of /apply Step 6.
/scrape finds and dedupes postings; /apply evaluates one at a time in
depth. Nothing connects the two ends: after a scrape returns 20 jobs, the
user eyeballs a table to decide where to spend /apply effort. /rank is the
bridge: batch-score every new posting against the fit framework and return
a ranked shortlist.
How it works:
- Selects jobs with status "new" from job_scraper/seen_jobs.json (--all
re-ranks everything unapplied; a focus argument filters), excluding
anything already in job_search_tracker.csv
- Dispatches parallel general-purpose agents (~5 jobs each) that WebFetch
each posting and score the five dimensions from 04-job-evaluation.md.
The rubric (skill match areas, career goals, deal-breakers) is passed
inline per the same token-efficiency rules /apply uses; agents score
only from actually fetched content and mark dead postings expired,
never guessing from a title
- Triage depth by design: posting text vs. profile only - no company
research, no salary lookups. /apply's Step 1 evaluation stays
authoritative and always re-runs on handoff
- Aggregates with the framework's 30/25/15/30 weighting and verdict
bands; location deal-breakers veto regardless of score; deadlines
within 7 days get urgency flags and win ties
- Updates seen_jobs.json additively (status "ranked"/"expired" plus
rank_score/rank_verdict/rank_date) so /scrape dedup keeps working;
the tracker is read-only. Re-running is idempotent
Integration: job-scraper SKILL.md documents the new status values and
suggests /rank after large scrape batches; README (commands list, file
tree, quick-start step 4).
An ATS reads the compiled PDF's embedded text layer, not the rendered page,
and LaTeX can silently produce PDFs whose text extracts as garbage: icon
glyphs where contact details should be, (cid:*) markers from fonts without
Unicode mappings, interleaved lines from multi-column layouts. This matters
more now that /add-template lets users bring arbitrary templates. The
existing Step 5 loop verifies what a human sees; this adds verification of
what a parser sees.
New Step 5d in /apply (CV only - cover letters rarely go through keyword
screening; cleanup renumbered to 5e):
- Extract the CV PDF's text layer with pdftotext -layout. pdftotext
(poppler) is an optional dependency: if missing, the mechanical check is
skipped with a warning and keyword coverage falls back to the visual PDF
read - the same graceful-skip pattern as salary_lookup.py
- Parseability checks verified against a real extraction of the stock
template: email/phone must survive as literal text (fontawesome icons
extract as harmless glyph-name noise like MOBILE-ALT/Envelope, but a
contact detail carried only by an icon or hyperlink is invisible to ATS),
no (cid:*) or replacement-character garbage, reading order matching
visual order, dates present
- Keyword coverage reuses the required/preferred list from Step 1, matched
in the posting's language, reported as covered / synonym-only /
missing-have-it / missing-gap. Honesty rule enforced: keywords the
profile genuinely supports get added to experience bullets; genuine gaps
stay visible, never stuffed
Integration: CLAUDE.md verification checklist section, ATS Parseability
guidance in 05-cv-templates.md, narrow Bash(pdftotext:*) entry in the
pre-approved permissions (keeping with the tightened scope from #27),
cv/*.txt gitignored (extraction is personal data; also deleted by the
step itself), and optional-dependency docs in README and SETUP.
The README has always invited users outside Denmark to build equivalents of
the four Danish portal CLI skills, but doing so meant reverse-engineering
.agents/skills/*/cli/ by hand. /add-portal turns that invitation into a
guided workflow:
- Interviews the user for the portal URL, skill name, market/language
(trigger phrases in the local language, like the Danish skills), and a
realistic test query
- Investigates the portal before writing code: search-URL pattern, result
structure (JSON API preferred over HTML), detail-page pattern, robots.txt
and access rules. Auth-walled portals are declined; portals with
restrictive terms get a prominent personal-use-only warning in the
generated SKILL.md (same as linkedin-search)
- Scaffolds from the canonical structure with linkedin-search as the
zero-dependency reference, enforcing the shared portal-skill contract:
search/detail commands, common flags, {meta, results} JSON shape, stderr
JSON errors, backoff on 429/5xx, chunked parsing
- Mandatory live test-run (search + detail + test suite) before registering
- Optionally wires the portal into /scrape via search-queries.md
The generator is country-agnostic; its output is market-specific and stays
in the user's fork, matching the repo policy that upstream remains a
universal template.
Docs: README (commands list, file structure, Job search tools section) and
SETUP.md (CLI install section pointer).
Users could already swap the stock moderncv/cover.cls templates, but only by
hand-editing the guidance in 05-cv-templates.md and 06-cover-letter-templates.md.
/add-template automates that:
- Interviews the user for the template's instructions: compile engine, fonts
(bundled files or system), style rules to preserve, and hard page limit
- Stores the template profile-agnostic ([PLACEHOLDER] tokens) under templates/
with a TEMPLATE.md manifest, so templates are safe to commit and share
- Runs a mandatory test compile with dummy data before registering anything
- Activates via a single managed block in 05/06, which /apply already reads,
so no changes to the /apply workflow are needed; --use default is a clean
revert to the stock templates
- --list and --use <name> manage multiple registered templates
Docs: README (commands list, file structure, LaTeX templates section) and
SETUP.md (compile section pointer).
* fix: move scoped permissions to settings.json, drop curl, untrack settings.local.json
Addresses #23:
- Remove pre-approved Bash(curl:*) - no agent workflow uses curl, and a
toolkit that routinely feeds untrusted job postings to the model should
not ship a pre-approved exfiltration-capable command
- Move shared permissions to .claude/settings.json (committed by
convention) and scope them tighter: Bash(bun run:*) for the job portal
CLIs, Bash(python/python3 salary_lookup.py:*) for salary lookups
- Untrack .claude/settings.local.json - it was committed despite being
listed in .gitignore; the file stays local for personal overrides
Reported-by: @josealfonsomora
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(setup): warn existing cloners about stale settings.local.json
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* refactor: fold /setup_docs into /setup as third onboarding path
- Auto-detect documents/ at /setup Step 0; offer three paths (documents
folder, single CV import, interview).
- Inline document-driven merge logic into setup.md as Path A: read-before-
write, additive vs. conflicting bucket merge with per-conflict prompts,
inference labeling for behavioral and style files, STAR-stub generation
instead of fabrication.
- Step 3 substeps for skill files skip when Path A populated them; non-
skill substeps (CLAUDE.md, cv/main_example.tex, search-queries.md)
always run. Path C interview preserved verbatim. /setup --section <name>
update flow preserved.
- Delete .claude/commands/setup_docs.md.
- Update /reset prompts to point only at /setup. Update documents/README.md
to reference /setup throughout. Update README.md to drop the standalone
/setup_docs section and add upskill/ to the skills tree.
- Remove the trailing comma left in .claude/settings.local.json after the
PR #6 revert (was technically invalid JSON).
Single onboarding entry point. documents/ folder convention unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(README): finish readme alignment for the /setup fold
- Update onboarding-paths tip to reflect three paths (was "Either"
before /setup_docs landed in PR #6).
- Add documents/ and upskill/ entries to the file tree, both relied
on by the new /setup Path A and by /upskill respectively.
- Add a brief "Other commands" section briefly describing /expand and
/upskill so users discover them without having to read individual
command files. /reset stays in the existing "Starting over"
subsection, now linked from the new section.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds four contributions from @Michael-Bach:
- /setup_docs - document-driven profile population from a documents/ folder (CV, LinkedIn export, diplomas, references, past applications). Idempotent merge with explicit additive vs. conflicting buckets and per-conflict prompts.
- /reset - typed-RESET confirmation gate for clearing profile data and/or documents folder contents.
- /expand - additive competency enrichment from documents and public URLs already in the profile (GitHub repos, portfolio sites), with web-searched syllabus lookups for named courses and certifications.
- /upskill - skill-gap analysis vs tracked jobs (or single URL), produces a prioritized heatmap and learning plan with year-tagged WebSearch queries.
Also adds the documents/ folder convention with README and gitignore entries for personal output files.
Closes#6
Two additions to 05-cv-templates.md so the /apply workflow follows the
same patterns as cv/main_example.tex:
1. Add the three \renewcommand* lines to the documented preamble so
downstream CVs inherit the color1 overrides for firstname, lastname,
and section headings. Includes a short paragraph explaining the
reason and a note for users who prefer regular-weight firstname.
2. Add a "Spacing inside itemize lists" subsection that calls out the
inter-item \vspace anti-pattern with a wrong/right example and
explicitly preserves the two related patterns that DO render fine
(post-section spacer, inter-cventry \vspace{3pt}).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings 02-behavioral-profile.md back into the reviewer's reading list (it
was dropped in the earlier token-efficiency refactor). The reviewer now
reads four files: 01 (candidate profile), 02 (behavioral profile), 03
(writing style), 04 (job evaluation). The LaTeX template files (05, 06)
remain excluded, since those govern structural/LaTeX concerns the drafter
already applied.
The behavioral profile is there to let the reviewer cross-check whether
the cover letter's voice matches the candidate's natural register -
something 03-writing-style.md does not encode (03 has the universal rules
like "no em-dashes, no cliches"; 02 has the per-person register like
"Collaborator PI should not be given a combative solo-hero tone").
Also cross-references 02 explicitly in the Part B "Tone and style issues"
category so the reviewer actually uses it during critique rather than
just skimming it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit of master vs the branch found three real coverage gaps in the
reviewer prompt and drafter's revise step. This commit restores them
without giving up the Part A (JSON edits) + Part B (narrative) structure.
1. Action-oriented reframing is restored as a Part B narrative category.
The previous refactor folded reframing into Part A edit rationales, but
structural weakness (e.g. "the whole opening paragraph is passive") does
not fit a clean find-and-replace and needs prose critique to surface.
2. Tone and style phrasing is strengthened from "anything that conflicts
with 03-writing-style.md" back to the master's proactive "check
against... flag issues with tone, formality, or voice", with explicit
examples (cliches, hedging, over-humility, inconsistent register).
3. Step 4 revise guidance is expanded from "Apply Part A... Apply Part B
using judgment" (which could be read as a free-for-all) to an
enumerated sub-list under Part B covering every category the reviewer
returns: missed keywords, company angles, action-oriented reframing,
tone/style. Keeps the architectural change (Part A/B split) but
restores the master's explicit "apply every kind of fix" discipline.
Also adds a small instruction to the reviewer: produce each Part B
category even if the finding is "no issues" - silence on a category can
be mistaken for skipping it.
One remaining judgment call left unchanged in this commit: the reviewer
no longer reads 02-behavioral-profile.md. The refactor excluded it on the
reasoning that 03-writing-style.md carries the voice/tone rules. This is
a conscious trade-off; if voice-matching starts slipping in practice,
restoring 02 to the reviewer's reading list is a one-line fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new Step 5 to the /apply workflow that compiles the CV (lualatex)
and cover letter (xelatex) and visually inspects the resulting PDFs before
presenting them to the user. This catches page-break failures that .tex
review cannot detect: orphaned cventry titles, cover letters spilling to
a second page, and bullet fonts not matching body text.
Also introduces relevance-weighted cutting as the rule for shrinking an
overflowing CV: score each candidate line by (a) relevance to the posting,
(b) uniqueness in the document, and (c) load-bearing role in the cover
letter, then cut the lowest-total-score line first, regardless of section.
This replaces the old static "cut oldest section first" heuristic, which
was wrong whenever a relevant older-role bullet competed with an
irrelevant recent-role bullet.
Bundled with the earlier pending token-efficiency improvements to the
/apply workflow (inline draft passing to the reviewer, scoped reviewer
file reads, single end-of-workflow verification pass).
Changes:
- .claude/commands/apply.md: new Step 5 (Compile & Inspect PDFs), renumber
old Step 5 to Step 6, update token-efficiency rules at top to reference
Step 6 for the verification checklist
- .claude/skills/job-application-assistant/05-cv-templates.md: switch
recommended compile engine from pdflatex to lualatex (pdflatex fails on
modern MiKTeX with fontawesome5 font-expansion errors), add
"Compile-and-Inspect Loop" section with needspace/enlargethispage
patterns, add "Relevance-weighted cutting" section
- .claude/skills/job-application-assistant/06-cover-letter-templates.md:
add compile command, add "Compile-and-Inspect Loop", document the
"\lettercontent{} + itemize" pitfall and the required font-matching
Raleway-Medium wrapper fix
- CLAUDE.md: add "Compiled PDF verification (MANDATORY)" checklist items
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>