Commit Graph
36 Commits
Author SHA1 Message Date
Mads LorentzenandClaude Fable 5 cffacfdde0 feat(portals): ship the Danish demo portals disabled, /setup enables them for Danish-market users (#288)
A non-Danish user's /scrape ran all four Danish boards by default,
spending tokens on irrelevant listings. The portals stay in-tree as the
maintainer's demonstration instance, one flag away.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 08:02:11 +02:00
fcefb8150f fix(web-research): stop treating a WebFetch 403 as a dead posting (#277)
* fix(web-research): stop treating a WebFetch 403 as a dead posting

WebFetch sends a bot user agent, and many bank and corporate sites answer
with HTTP 403 while serving the same page to a browser normally. Every
command treated that as "page unavailable" and degraded silently rather
than failing loudly:

- /rank marked live postings `expired`
- /apply fell back to search snippets, or to vague cover-letter prose
- /scrape stored listing-page `#fragment` URLs, which fetch fine and
  return unrelated jobs, so every later /rank and /apply run on that
  entry failed

Adds 09-web-research.md as the single reference: the trust boundary, a
curl browser-header retry with a tag-stripping extractor, a four-step
escalation order, the login-wall case, why the employer's own careers
posting beats an aggregator listing (the requisition ID and the grade
survive there), and the rule that a search-result snippet is a lead
rather than a source.

Wires it into /apply, /rank, /interview, /outcome, /notion-sync, the
job-scraper skill, and writing-style rule 5. Bumps 03-writing-style.md
to 1.2.0; 09-web-research.md starts at 1.0.0.

Aggregator examples are given generically (LinkedIn, Indeed, national
job boards) so the guidance holds in any market.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(web-research): gate the browser-header retry on robots.txt

Addresses review feedback on #277.

WebFetch identifies itself as Claude-User and honors robots.txt, so a 403 has
two very different causes and they must not be treated the same: a WAF default
on a site whose published policy allows access, or a site that has actually
declined. Retrying with browser headers in the second case circumvents the very
opt-out mechanism site owners are told they can rely on, and the core framework
cannot hold a looser standard than it asks of community forks.

The escalation now runs tools/robots_check.py before the retry. A disallow for
"*" or for "Claude-User" skips the retry entirely and goes to step 3 (find the
employer's own posting). The rule is stated plainly in 09-web-research.md so
later edits do not erode it: the retry exists to get past bot-filtering
firewalls on sites whose robots.txt permits access; it is never used to
override a site that has said no.

Two findings from testing the gate against live sites, both pinned by
tests/test_robots_check.py (15 offline cases):

- The WAF usually blocks robots.txt too. privatebank.barclays.com returns 403
  on the policy file to Claude-User and 200 to a browser, so a naive gate would
  block the retry on exactly the sites the retry is for. The checker reads the
  policy as a browser when the honest request is refused, then obeys it
  strictly - a policy you are prevented from reading cannot be honored, and
  robots.txt is not the protected resource.
- urllib.robotparser cannot be used. It ends a record at a blank line and
  matches rules in file order, so Barclays' real file (blank lines between
  "User-agent: *" and its rules, "Allow: /" before "Disallow: /cs/") reads as
  everything-allowed. That fails open, in the one direction that matters. The
  checker implements RFC 9309 longest-match instead, with ties resolved to
  Disallow rather than Allow.

Verified live: barclays /careers/ allowed and /cs/ blocked, ubs.com allowed,
jobup.ch /api/ blocked while /en/jobs/ stays allowed. 09-web-research.md
1.0.0 to 1.1.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: kgb <kevingblackman@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 20:36:55 +02:00
Gabriel Ignacio MensiandClaude Sonnet 5 6392ca1628 Add a Language Gate - no dimension currently checks language requirements (#275)
Nothing in the framework checks a posting's language requirements
against what the candidate actually speaks. It is not one of the five
Scoring Dimensions in 04-job-evaluation.md, it is not checked in
/scrape's Step 3 fit assessment, and it is not a field in /rank's JSON
output - even though /apply's Step 1 already extracts a posting's
required language generically, with nowhere to report a mismatch to.

This adds a Language Gate, structured like the existing Eligibility
Gate (read the posting, classify against profile data, hard-stop on a
real mismatch), built on a new structured Languages table in CLAUDE.md
/ 01-candidate-profile.md. /setup now asks for it directly (Path C), or
infers it from a CV/LinkedIn export (Paths A/B - LinkedIn exports
already carry a self-rated Languages section).

The gate compares a posting's stated language requirements against
that table with three outcomes:

- Requires a language not declared at all -> hard FAIL, never
  presented.
- Requires a higher level in a language that is declared (e.g. "fluent
  English" against a declared B1/B2) -> FLAG, not an auto-reject -
  scored and drafted normally, with the gap surfaced so the candidate
  judges it themselves (a "fluent" bar reads very differently from a
  strict employer vs. one that's flexible on it).
- Requires a language at or below the declared level -> clean PASS.

Wired through the three places that need it: /scrape (Step 3), /rank
(new language_gate/language_note fields alongside the existing
location veto - both are now persisted to seen_jobs.json, not just
used transiently to decide one run's shortlist), and /apply (Step 1's
language extraction now has somewhere to report to).

Out of scope, deliberately: this does not touch the free-form
Deal-breakers list or how it's used elsewhere (e.g. Scoring Dimension
4's relocation check) - that's a separate question this change takes
no position on.

Validated with two live-testing passes against real, unfetched
postings (not fabricated text) across 3 portals and 3 market languages
(Danish, German, Spanish/Argentina): 8/8 postings gated correctly in
the first pass, including ambiguous real-world wording ("you
communicate well in English") a rigid rule would have gotten wrong. A
second pass, run specifically to force a hard-FAIL case, found one
(a Danish posting requiring the ability to read Danish) and confirmed
it persists correctly and would be excluded from /rank's shortlist.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 18:46:59 +02:00
Mads LorentzenandClaude Fable 5 d4b406efff docs(privacy): stop calling a fork 'private working space'; warn at /setup time
Forks of public GitHub repositories are always public, so SETUP.md
section 8's 'your fork is private working space, so commit them' invited
exactly the personal-data exposure it seemed to rule out - the observed
failure mode behind several real forks that pushed filled-in profiles to
public master. Section 8 now states the fork-is-public fact plainly and
documents the safe alternative (private repo + template as upstream
remote), and /setup's completion summary carries a matching privacy note
at the exact moment profile data first lands in tracked files.

Also backfills the CHANGELOG entry for #265.

Prompted by rasstamann's discussion #266.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 22:03:22 +02:00
NotAbdelrahmanelsayed 1cdaf9497f feat(rank): persist triage gaps and strengths into seen_jobs.json (#263)
/rank's Step 2 scoring agents already return strengths and gaps per job,
but Step 4 only persisted rank_score/rank_verdict/rank_date - both arrays
were printed once in Step 5 and then discarded. Store them verbatim in
seen_jobs.json (replaced, not accumulated, on --all re-ranks) so downstream
consumers can read real triage findings instead of re-deriving them.

Discussed in #258.
2026-07-31 17:41:41 +02:00
Novica Nakov 82a60300b6 feat(add-template): make /add-template engine-agnostic (#238)
* feat(add-template): make /add-template engine-agnostic so Typst can register alongside LaTeX

/add-template hardcoded a lualatex|xelatex|pdflatex engine enum and .tex
assumptions, so custom templates could only be LaTeX. Replace the enum with a
declared source extension + compile command, so any toolchain (Typst via
`typst compile`, or others) registers the same way stock LaTeX templates did.

Stock CV/cover-letter pipeline stays LaTeX and untouched (per #181).

Also fixes a latent bug this surfaced: apply.md's compile step ignored the
ACTIVE-TEMPLATE block and always ran lualatex/xelatex on .tex regardless of
the active template, and .gitignore's cv/main_*.tex pattern would not have
ignored a non-.tex draft (personal-data leak). Both now resolve from the
declared extension/command.

* fix(add-template): satisfy security_guards on the .gitignore Typst fix

security_guards.py pins the personal-data ignore rules by exact string and
gates negations through an allowlist, so broadening cv/main_*.tex and
cover_letters/cover_*.tex to *.* (for .typ drafts) needed a matching update
to REQUIRED_IGNORE_RULES.

Also tighten the .gitignore itself per review: keep the re-include
negations at .tex instead of widening them to *.* too. The stock example
files are always LaTeX, so .tex is enough to re-include them, and a
wildcard negation would have also re-included build artifacts
(main_example.pdf/.aux) that should stay ignored. ALLOWED_IGNORE_NEGATIONS
needs no change since the negations are unchanged.

Also adds a CHANGELOG entry under Unreleased for the Typst/custom-template
support.
2026-07-26 16:21:33 +02:00
LeoWinston-9596andClaude Opus 4.8 41ddc0c73c RFC feat(08): application-form fields as a third /apply artifact (#212)
* feat(08): add application-form fields as a third /apply artifact

/apply produces a CV and a cover letter. Many applications need a third
thing: free-text typed into a portal. Graduate programs, large-employer
ATS systems and startup forms ask for self-introductions, structured
project entries, motivation questions and pitches under a hard character
limit - none of which either document covers, and all of which the
interviewer reads alongside the CV.

Governing rule: a form field selects from what is already true and
arranges it for the question asked. It never introduces a new claim.
All accuracy rules from 03 and 05 apply unchanged.

Covers three field types (self-introduction, structured project entries,
hard character limits), the output format (a plain .txt the candidate
pastes from, with counts stated and internal NOTE TO SELF blocks marked
as not-for-pasting), and a verification checklist.

Two places where form fields are stricter than a CV, because both are
easy to get wrong:

- Project entries carry a name and a role, so they read as ownership of
  the whole project in a way a terse CV bullet does not. Contributory
  work has to be scoped inside the description.
- Project dates are the dates of the project, not of the employment.
  Narrowing them is more accurate where the candidate can say when the
  project started - but never invent a boundary to improve the ratio.

Registers the file in SKILL.md (framework_version 1.0.1 -> 1.1.0) and in
the FRAMEWORK_FILES list in tools/check_upstream_updates.py, so it is
covered by the update check like every other framework file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fixup(08,apply): wire as optional /apply offer; align grounding to three-source union

- apply.md Step 6: offer the third artifact after CV/cover letter are
  produced, mirroring the /outcome house pattern for optional capabilities
  (offer, act only on yes, default output unchanged).
- 08-application-forms.md: ground claims against the framework's
  01-candidate-profile.md + master CV + CLAUDE.md union (per #185)
  instead of only 01, in both the governing rule and the checklist.

Per MadsLorentzen review on PR #212. Rebase onto merged #210 (the
tenure-check reference this file cites) still pending — #210 hasn't
landed on upstream/master yet.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 16:05:42 +02:00
LeoWinston-9596andClaude Opus 4.8 1969d0ea70 feat(apply,interview): write confirmed facts back to the profile in the same turn (#211)
* feat(apply,interview): write confirmed facts back to the profile in the same turn

The grounding audit added in #185 removes any claim the three sources do
not support. That is right, and it has a consequence the framework did
not close: the audit cannot tell a fabrication from a real fact the user
stated out loud in an earlier session. Both look identical to it - absent
from the sources - and both get stripped.

So a fact that surfaces in conversation and is never written down is lost
silently. A real metric the user confirmed disappears from every
subsequent CV, and nothing reports that it happened.

Adds a standing rule to /apply: when the user confirms, corrects or
supplies a fact not already in 01-candidate-profile.md, write it there in
the same turn and bump framework_version. 01 is one of the audit's three
sources, so the fact is grounded on the next run.

Adds the same exception to /interview rule 5, which previously forbade
touching profile files outright. Interview prep is where new facts
surface most often - a recalled metric, a corrected scope, a filled-in
STAR stub - and prep files are not a substitute for the profile.

Notes the source-consistency case explicitly: a fact added to 01 that
CLAUDE.md and the master CV do not mention is an absence, not a
contradiction, so it does not trip the audit's profile-consistency
warning. If the new fact corrects either of those, fix it there too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fixup(apply,interview): drop framework_version bump from write-back rule

Bumping 01's framework_version on a personal fact write-back corrupts
check_upstream_updates.py's upstream_version > local_version signal, and
diverges from /setup and /expand precedent where version tracks the
file's structure, not personal-data edits to its content.

Per MadsLorentzen review on PR #211.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 16:04:23 +02:00
NotAbdelrahmanelsayed c7a1e0cf89 fix(rank): include job posting URL in ranking tables (#236)
* fix(rank): include job posting URL in shortlist/below-threshold/excluded tables

/rank's output tables listed title, company, and score but dropped the
posting link, forcing the user to go dig it out of seen_jobs.json to
open a job they wanted to act on. The key in seen_jobs.json is already
the URL, so this is a formatting fix, not a new lookup.

* fix(rank): link to entry's url field, not the seen_jobs.json key

Some portals key seen_jobs.json entries by a company+title composite
rather than the URL, so [Link](<key>) could render a broken link.
Every entry carries a dedicated url field, which is always valid.
2026-07-25 19:38:02 +02:00
Ayobami Adegoke a0e81204da feat(outcome): add follow-up branch to chase quiet applications (#198)
Adds Step 2b to /outcome: surfaces open applications gone quiet (default 10 days), drafts a brief channel-appropriate follow-up in the candidate's voice using only claims from the already-submitted materials, and logs it (followed up marker in notes + followup_YYYY-MM-DD.md in the archive). Reachable from the no-arg pipeline table (now showing days-quiet and follow-ups-sent) and via /outcome followup [N|company]. Draft-only never send, capped at two follow-ups terminating into Step 2's existing no_response path, and a thank-you note offered the moment Step 3 records a completed interview stage. Reads the contact_person column nothing previously consumed.

Folded into /outcome rather than a standalone command - dependency-free (unlike /gmail-sync) and reusing detection, the archive, the notes ledger, and the resolution path already there. The invited return of #46 (closed stale, not on merit). Guardrail tests mirror the /notion-sync pattern; 10-vs-30-day threshold contrast with /gmail-sync documented and test-pinned.

By @ayobamiseun.
2026-07-20 20:37:22 +02:00
Yuan Chen 61d17d1bda feat(commands): add /gmail-sync - confirm-before-write status sync from Gmail (#170)
Scans Gmail (via the claude.ai Gmail connector) for status signals on open tracked applications - interview invites, assessments, offers, rejections - and proposes them as a sourced batch the user must approve before anything is written to job_search_tracker.csv or outcome.md. Never proposes hired/offer_declined (user's real-world decision), routes conflicting signals to manual /outcome, appends-only to Notes, idempotent by message ID, read-only against the mailbox, and degrades to one message + clean exit without the connector (the /notion-sync precedent). State is gitignored personal data.

By @chenyuan99, who raised the connector-dependency tension himself and restructured classification and writing into separate approval-gated steps.
2026-07-19 19:38:15 +02:00
Jovin Nicholas f51a766e72 feat(apply): factual grounding audit against the union fact base (#185)
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
2026-07-19 19:22:46 +02:00
Mads LorentzenandClaude Fable 5 122db059cb feat(apply): requirement-coverage rules and CV targeting improvements from output benchmark (#184)
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>
2026-07-18 22:22:05 +02:00
Mads LorentzenandClaude Fable 5 de91a97fe8 feat(apply): make CV language a profile setting, defaulting to English (#179)
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>
2026-07-18 08:39:51 +02:00
Mads LorentzenandClaude Fable 5 3847088986 fix(setup): ground Path A profile-statement extraction against the profile (#178)
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>
2026-07-18 08:39:17 +02:00
Mads Lorentzen fb91be7a0b security: treat job postings as untrusted input across /apply and /rank (#175)
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.
2026-07-17 21:48:59 +02:00
Jaewon Chung ac6a734e16 fix(apply): name CVs main_<company>_<role> to avoid overwrites (#171)
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.
2026-07-17 21:26:04 +02:00
Adri f1ed475d59 feat(notion-sync): one-way read-only pipeline view in Notion via MCP (#169)
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.
2026-07-17 21:26:02 +02:00
Yuan Chen 848eddbecc feat(html-report): add /html-report command for application tracker dashboard (#131)
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.
2026-07-16 21:48:59 +02:00
Yash Rajeshbhai DarjiandCursor 0a8fc194e5 docs: sync onboarding with linkedin-search + freehire-search reality (#150)
/setup still told non-DK users that built-in CLIs are Denmark-specific.
Align setup, /add-portal, and search-queries.md with shipped country-agnostic
CLIs and /scrape auto-discovery (post #85 / #95 / #102).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 14:30:49 +02:00
Mads LorentzenandClaude Fable 5 855b6725f3 brand: tagline line + the hired-moment coffee ask (#136)
* 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>
2026-07-12 11:17:48 +02:00
Ayobami Adegoke 8bee3ddb97 docs(add-template): clean all test compile artifacts (#130) 2026-07-11 22:06:04 +02:00
Ayobami Adegoke 379abad503 docs(add-template): clarify --use activation metadata (#129) 2026-07-11 22:05:05 +02:00
Sai Sridhar Tarra 30e8b44759 docs: mention /interview as next step after /apply (#108) (#110) 2026-07-10 08:04:23 +02:00
Ayobami Adegoke 75fbfc56e4 feat: add /interview command for stage-specific interview prep from the application archive (#58)
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.
2026-07-08 17:08:32 +02:00
Ayobami Adegoke 4488290274 feat: add /outcome command to record application results and close the calibration loop (#54)
/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.
2026-07-07 19:40:12 +02:00
Ayobami Adegoke fce2cf23c0 feat: add /rank command to triage scraped jobs into a ranked shortlist (#43)
/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).
2026-07-07 17:31:31 +02:00
Ayobami Adegoke 2551824ecb feat: add ATS & keyword verification pass to /apply's compile-and-inspect loop (#40)
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.
2026-07-07 06:28:59 +02:00
Ayobami Adegoke 79b153764d feat: add /add-portal command for generating local job-portal search skills (#37)
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).
2026-07-06 21:04:44 +02:00
Ayobami Adegoke 6b144dc456 feat: add /add-template command for registering custom LaTeX templates (#30)
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).
2026-07-05 20:27:33 +02:00
Mads LorentzenandClaude Opus 4.7 974b29ec3a refactor: fold /setup_docs into /setup as third onboarding path (#9)
* 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>
2026-04-29 09:57:33 +02:00
Bach 2ba441f3fb feat: add /setup_docs, /reset, /expand commands and /upskill skill
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
2026-04-29 09:30:08 +02:00
Mads LorentzenandClaude Opus 4.7 fa9c422d26 feat(apply): restore 02-behavioral-profile.md to reviewer reading list
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>
2026-04-24 20:14:59 +02:00
Mads LorentzenandClaude Opus 4.7 906ac1bc19 fix(apply): restore QC coverage dropped during earlier token-efficiency refactor
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>
2026-04-24 20:12:39 +02:00
Mads LorentzenandClaude Opus 4.7 32e63e5143 feat(apply): add mandatory PDF compile-and-inspect step and relevance-weighted cutting
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>
2026-04-24 19:34:46 +02:00
AI Job SearchandClaude Opus 4.6 c66d599d75 Initial release: AI-powered job application framework
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 08:34:06 +01:00