Commit Graph
47 Commits
Author SHA1 Message Date
LeoWinston-9596andClaude Opus 4.8 2e654d68d2 feat(05): state in-progress qualifications explicitly; check tenure against output (#210)
Two profile-accuracy rules for the CV guide.

**In-progress qualifications.** A bare year range is not enough: an entry
reading 2025-2026, seen partway through 2026, reads as a finished degree,
because a skimming reader treats a closed range as closed. A profile
statement saying "currently completing" does not fix it - the education
entry is where a reader checks the credential, so it has to stand alone.
Claiming a credential not yet held is discovered at transcript or
reference check rather than at interview, and it costs nothing to
prevent. Adds the LaTeX form and a check that the profile statement,
education entry and any availability note agree on one completion date.

**Tenure against visible output.** A two-year role represented by a
single project reads as low output whether or not that is fair; the
reader cannot know what filled the time, so they guess. Hits career
changers, long-cycle work (industrial, clinical, research) and anyone
kept on a single account. Three honest fixes in preference order -
surface more real work, make the phases within the role explicit, name
what made the cycle long - and an explicit prohibition on the two
dishonest ones: never pad with invented projects, never quietly shorten
employment dates. Both are discoverable and worse than the perception
problem. If the ratio survives the fixes the interview question is
coming, so the answer belongs in interview prep rather than improvised.

framework_version 1.2.0 -> 1.3.0.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 16:03:47 +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
Lautaro Emanuel JimenezandClaude Sonnet 5 7db231c680 feat(job-scraper): flag mass-posting and recycled-listing patterns (#207)
* feat(job-scraper): flag mass-posting and recycled-listing patterns

Adds Step 2.5 to detect two distribution patterns that are worth
surfacing to the user as a caution signal, not an accusation:

- Mass-posting: the same (or near-identical) listing posted across
  many cities/locations at once, consolidated into one row instead of
  presented as separate duplicate results.
- Recycled listing: a new candidate whose description closely matches
  an older seen_jobs.json entry from the same company, but under a
  different title.

Neither pattern is treated as proof of anything - fit is never
downgraded and results are never excluded because of it, the point is
giving the user the signal so they can decide. Explicitly scoped away
from naming companies as fraudulent (see the new Important Rules #8):
this documents a detectable behavior pattern, not a blacklist.

Motivated by real signal seen today: the same req ID posted across 6
different LATAM cities, and a single-city listing duplicated 3x under
slightly different titles.

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

* Fix non-executable Pattern B: add snippet field, move check to Step 4

Pattern B instructed comparing a new posting's description against
existing seen_jobs.json entries, but the Step 4 schema never stored
descriptions - nothing existed to compare against, so the check
couldn't run as written.

- Adds an additive `snippet` field to the seen_jobs.json schema
  (same move as #193's `portal` field), populated when each entry is
  written.
- Moves the recycled-listing check itself to Step 4, where the write
  happens, leaving Step 2.5 as the in-run mass-posting check only.
- Makes the snippet match the actual discriminator, not company +
  different title alone - that alone would false-flag every company
  that legitimately runs several concurrent open roles, which is
  exactly what Rule 9's "signal, not accusation" framing is trying to
  avoid.
- Updates the Step 5 / Important Rules cross-references accordingly.

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

* Trim to within-run mass-posting detection only

Drop the cross-run recycled-listing check (Step 4) and the snippet
field it depended on, per review: no evidence the pattern recurs
often enough to justify persisting a description snippet on every
seen_jobs.json entry permanently. Step 2.5's in-run mass-posting
consolidation is cheap and stays.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 20:53:45 +02:00
LeoWinston-9596 d88c023683 feat(04): add work-authorization eligibility gate to job evaluation (#209)
Adds a work-authorization eligibility dimension to 04-job-evaluation.md: whether the candidate is legally permitted to hold the role (citizenship/PR/visa requirements) as a hard filter, distinct from permit-timing. Country-agnostic, quotes the requirement source verbatim, treats silence as not-permission, and surfaces to the user rather than silently dropping. framework_version bumped 1.0.0 -> 1.1.0.

By @LeoWinston-9596 (split from #199). Note: the referenced /setup 'second gate' permit-timing collection isn't wired yet - a natural follow-up.
2026-07-21 08:11:53 +02:00
Lautaro Emanuel Jimenez 3d8689a56d docs(cv): flag hardcoded English section headings for non-English CVs (#200)
The moderncv template's section headings (Core Competencies, Professional Experience, Education, Languages, Publications, Honors and Awards, References) and the References boilerplate line are literal English text the workflow never translates, so a CV localized in prose can sit under English scaffolding. Adds an illustrative, fork-aware rule to 05-cv-templates.md (translate the headings too, whatever your template defines) plus a verification-checklist item in CLAUDE.md. Ties into the CV-language profile setting from #179.

By @Lautaro073. Heading list corrected against the stock template on review (Publications/Honors and Awards in, non-existent Independent Projects out; made illustrative for forks).
2026-07-21 07:38:03 +02:00
Ayobami Adegoke a5de938492 feat(scrape): portal health check to catch silent scraper rot (#193)
Adds Step 4.75 to /scrape: detects the failure mode where a portal changes markup and its parser exits 0 with zero or garbled results (invisible to the Step 1c fallback, which only fires on non-zero exit). Free pass over this run's results (degraded scan) plus seen_jobs.json yield history; bounded escalation on suspicion only (the portal's own SKILL.md test query, one broader retry, rate-limit never treated as evidence); health: lines in the Step 5 summary with a confirmation-gated enabled:false quarantine offer; healthy portals stay silent. Adds a /scrape health [portal] probe-only mode. Persists a portal field in seen_jobs.json additively (per the /rank precedent), with read-time URL-domain attribution for pre-field entries so no migration is needed.

Folded into /scrape rather than a standalone /doctor command - the detection lives where the evidence (run results + yield history) already is, and a routine command catches rot a user would otherwise notice weeks late. By @ayobamiseun.
2026-07-20 21:53:51 +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 12717d2c12 fix(templates): correct 06 structure-block antipattern and 05 needspace scope (#183)
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>
2026-07-18 22:19:02 +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
Erik Pastor Rios 37595187d6 feat(job-scraper): per-portal enabled toggle honored by /scrape (#160)
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.
2026-07-15 20:20:37 +02:00
Rasika d1e707ef1f feat(job-scraper): referral-contact LinkedIn search links for high/medium-fit jobs (#149)
* added contact skill

* refactor(job-scraper): fold referral-contacts into Step 4.5, drop contacts cache
2026-07-15 07:52:04 +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
Erik Pastor Rios b20467df4a feat(versioning): framework_version markers, CI version guard, and fork update checker (#144)
* 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
2026-07-14 14:29:24 +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
Erik Pastor RiosandClaude Opus 4.8 7e8df35819 docs(job-scraper): make search queries market-neutral (#95)
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>
2026-07-10 08:06:54 +02:00
Sai Sridhar Tarra 30e8b44759 docs: mention /interview as next step after /apply (#108) (#110) 2026-07-10 08:04:23 +02:00
Sai Sridhar Tarra fea59fd8df docs: document complete seen_jobs.json schema including rank fields (#107) (#111) 2026-07-09 21:32:43 +02:00
Yash Rajeshbhai DarjiandCursor 3bb4688e5d Use CLI detail in scraper Step 2; broaden skill description (#102)
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>
2026-07-09 21:12:15 +02:00
Mads LorentzenandClaude Fable 5 a558c8593b fix: rename job-scraper skill to 'scrape' so /scrape resolves natively (#74)
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>
2026-07-08 18:51:18 +02:00
Mads LorentzenandClaude Fable 5 a5ffcc39ff chore: untrack tracker CSV, scope scraper Bash permission, fix portal SKILL.md paths (#71)
- 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>
2026-07-08 17:14:39 +02:00
Ayobami Adegoke 6477987608 ci: add GitHub Actions workflow - LaTeX smoke compiles, skill lint, CLI typechecks, placeholder integrity (#59)
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.
2026-07-08 17:09:33 +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
PRATHAM KUMARandrajpratham1 1ac677dd7d 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)
* 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>
2026-07-08 17:08:12 +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 Fable 5 f15b9fb65d Tighten pre-approved permissions and untrack settings.local.json (#27)
* 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>
2026-07-05 10:56:17 +02:00
Deepak Dhungel 37a0eed6e7 fix(skills): replace bold markdown with proper YAML Frontmatter (#16) 2026-06-15 21:41:10 +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 465c429f94 docs(skill): document moderncv color overrides and itemize spacing rule
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>
2026-04-28 21:03:02 +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