Commit Graph
177 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
Mads LorentzenandClaude Opus 4.8 b204c44fdb chore(funding): add GitHub Sponsors button alongside Ko-fi (#240)
GitHub Sponsors profile for @MadsLorentzen is now live and accepting
sponsorships. Add it to FUNDING.yml so the repo's native "Sponsor this
project" box links to both GitHub Sponsors and Ko-fi, meeting developer
and non-developer supporters on whichever path is lowest-friction for
them. Ko-fi remains unchanged; the README Ko-fi block is untouched.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 21:59:09 +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
Johnson K C aa7c707399 fix(convert_salary_excel): store standalone count columns as counts, not indexes (#230)
An unmatched count column (e.g. a lone total headcount with no paired index
column) was appended as an untyped standalone value and stored under "index",
even though detect_column_type had already classified it as a count.
salary_lookup then rendered the raw headcount as a salary index with a
meaningless "vs baseline" percentage.

Tag unmatched count columns with field="count" so the row parser stores them
under "count" (as an int, matching the paired-count branch). Standalone index
and untyped columns are unaffected.
2026-07-23 10:32:22 +02:00
Ilya Strelov 1ae66ad094 chore(freehire-search): point at freehire.me (domain migrated from freehire.dev) (#229)
freehire moved its primary domain from freehire.dev to freehire.me. Update the
freehire-search skill's default API base URL, help text, docs, and examples.

Backward-compatible: FREEHIRE_API_URL still overrides the base (self-hosting),
and normalizeSlug is host-agnostic so pasted freehire.dev/jobs/<slug> URLs still
resolve. The GitHub repo link (github.com/strelov1/freehire) is unchanged. All
27 CLI tests pass; the freehire.me API answers 200 for /jobs/search + /jobs/facets.
2026-07-23 10:17:10 +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
Oscar Madera 3609f584b5 fix(convert_salary_excel): pair count/index columns by category name, not adjacency (#219)
The sequential scan assumed count/index pairs are always adjacent.
Interleaved columns like Count_A, Count_B, Index_A, Index_B produced
wrong pairings (Count_B ↔ Index_A), silently corrupting data.

Now columns are grouped by type, then matched by the category name
derived from stripping type words. Unmatched columns fall back to
standalone value columns using the original header name.
2026-07-22 20:34:48 +02:00
Mads LorentzenandClaude Opus 4.8 a68028bc54 fix(cli): pin @types/bun and @bunli/* to concrete versions to stop CI type-drift (#226)
CI runs `bun install` (not --frozen-lockfile) and the CLIs' package.json
pinned @types/bun, @bunli/core, and @bunli/utils to "latest", so each fresh
install could resolve a different version than the lockfile. When a "latest"
bun-types resolved that didn't satisfy the tsconfig (lib: ["ESNext"] with no
DOM, types: ["bun-types"] as the only source of Response/URL/fetch globals),
`bun run typecheck` failed across every .ts file - a transient red on PRs that
never touched TypeScript (observed on #207, which changes only SKILL.md).

Pin the three previously-floating dev/framework deps to the versions the
lockfiles already resolve, so behavior is unchanged and the drift class is
gone:
- @types/bun: latest -> 1.3.14  (all 6 CLIs)
- @bunli/core:  latest -> 0.9.1  (jobbank, jobdanmark, jobindex, jobnet)
- @bunli/utils: latest -> 0.6.0  (jobbank, jobdanmark, jobindex, jobnet)

The ^-ranged deps (node-html-parser, zod, typescript) are left as-is; they are
semver-guarded and were not the cause. No lockfiles committed (bun.lock stays
gitignored per existing policy). Verified: all 6 CLIs install and `bun run
typecheck` clean with the pins.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 17:18:46 +02:00
Mads LorentzenandClaude Opus 4.8 905f6e0946 docs(releases): add CHANGELOG + release-based update guidance; sharpen real-path bar (#225)
Addresses #213 (how to keep up with a fast-moving upstream) and closes the
verification loophole surfaced in the 2026-07-22 triage audit.

- Add CHANGELOG.md (Keep a Changelog + semver), with v1.0.0 as the first
  tagged baseline and an Unreleased section for going forward.
- SETUP.md section 8: recommend updating to a tagged release (a vetted,
  described checkpoint) over pulling raw master; fetch --tags and merge a tag.
- README: add a "Staying up to date" pointer to Releases, the CHANGELOG, and
  check_upstream_updates.py.
- CONTRIBUTING.md: sharpen "Claims get verified" - a test that distinguishes
  master from the fix is necessary but not sufficient; the failing input must
  be one the workflow actually produces, not one the test hand-builds. Fixes
  demonstrated only through a synthetic input the real code path never receives
  get declined even when their test is green.

Note: the git tag / GitHub Release for v1.0.0 is intentionally left for the
maintainer to cut.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v1.0.0
2026-07-22 11:36:47 +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
Oscar Madera 78281e8bea fix(jobdanmark): narrow soft-404 detection to avoid rejecting real postings (#206)
The jobdanmark detail parser flagged a soft-404 by testing whether the page title contained the substring '404' anywhere, so a legitimate posting titled e.g. 'Room 404 Cleaner' was wrongly rejected as NOT_FOUND. Narrows title matching to startsWith('404') plus specific error phrases ('page not found', Danish 'siden blev ikke fundet'), keeping the existing body-text backstop. Verified: strictly reduces false-positives, the real 404-page title ('404 | Jobdanmark') still detected, tests pass network-free.

By @oscarbol09.
2026-07-21 08:11:20 +02:00
Oscar Madera d3eea27b90 fix(portals): depth-track div extraction so nested job descriptions aren't truncated (#204)
The jobindex and linkedin detail parsers matched description containers with a non-greedy regex that stops at the first inner </div>, so any posting whose description contains nested divs was silently truncated (jobindex dropped later sections; linkedin dropped everything after the first block). Replaces the regex with a depth-tracked extractDivContent scanner that walks div open/close markers to the matching close. Verified: truncation bug reproduced against real markup fixtures, depth arithmetic correct (no off-by-one/infinite-loop), 28 tests pass network-free, no regression on non-nested divs. Malformed-HTML over-grabs rather than truncates - the safer failure, cleaned by downstream stripTags/decode.

By @oscarbol09.
2026-07-21 08:11:17 +02:00
LeoWinston-9596 808be3daad fix(privacy): ignore scraper state and interview records at any depth (#208)
job_scraper/seen_jobs.json (and notion_sync.json / *.md) were ignored by a repo-rooted pattern, but the job-scraper skill resolves job_scraper/ relative to its own directory, so the state file lands at .claude/skills/job-scraper/job_scraper/ and the rule never matched - publishing every scraped posting with fit scores and skip-reasons on a public fork. Switches to **/-prefixed patterns that match at any depth (the rooted location still matches too, so no regression), and adds documents/interview/** (interview prep names employers, quotes submitted material, and lists the candidate's weak points) - it was never ignored though documents/applications/** was. REQUIRED_IGNORE_RULES updated in lockstep so the security guard stays in sync.

By @LeoWinston-9596 (split from #199). Verified: both nested and root seen_jobs.json now ignored, interview records ignored, guard suite green (17 tests incl. #195's negation checks). Rebased cleanly on current master.
2026-07-21 07:39:56 +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
Oscar Madera 73fb71587c fix(cli): catch unhandled promise rejections in self-contained CLIs (#203)
The freehire and linkedin CLIs called main().then(code => process.exit(code)) with no .catch(). If main() throws or returns a rejected promise, the .then() never runs, process.exit() is never called, and the runtime terminates with exit code 0 - a runtime failure becomes indistinguishable from success (and would suppress the Step 1c WebSearch fallback, which keys on non-zero exit). Adds a .catch() that writes the same JSON error shape the rest of each file already uses ({error, code: INTERNAL_ERROR} to stderr) and exits 1. Scoped to the two self-contained CLIs only; the bunli portals catch internally via defineCommand.

By @oscarbol09.
2026-07-20 21:54:10 +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
Oscar Madera b3b351605c fix(salary): detect city column from header token, not exact match (#201)
convert_salary_excel.py detected the city column via exact membership (h_lower in CITY_PATTERNS), so real headers like "City Name", "City/Kommune", or "Kommune <suffix>" never matched and every company was written with an empty city field. Switches to header_matches(h, CITY_PATTERNS) - the same whole-token matcher already used for the company, count, index, and ID columns. Same bug class as #151 (company column); bare "City"/"Kommune" inputs are unaffected. Regression test covers bare and suffixed headers.

By @oscarbol09.
2026-07-20 20:20:53 +02:00
Thejesh Reddy 9cad956cc9 fix(portals): add a 15s request timeout to every board fetch (#197)
None of the board CLIs set a fetch timeout, and the retry loops react only to HTTP status codes, not to a connection that is accepted then never responds (black-holed TCP, hung TLS, stalled proxy) - so await fetch(...) never settles and the command hangs with no output and no exit. freehire's helper even documented a fast-degrade contract its try/catch didn't deliver on a mid-flight stall. Adds signal: AbortSignal.timeout(15000) to every fetch across all six CLIs, with network-free tests asserting the signal is present on each request wrapper.

By @thejesh23. Verified: 8 timeout tests pass locally with fetch stubbed (no network), and would fail on the pre-fix code.

Closes #196
2026-07-20 20:19:27 +02:00
Thejesh Reddy 36462e356e fix(security_guards): reject un-allowlisted .gitignore negations (#195)
check_gitignore() verified each required personal-data rule was present via set membership, but .gitignore is order-sensitive: a later !pattern re-includes a file an earlier rule excluded, so the required line stays physically present while the file is no longer ignored - the guard failed open on exactly the weakening its docstring claims to catch. Keeps the required-rules-present check and additionally rejects any negation line outside a small reviewed ALLOWED_IGNORE_NEGATIONS allowlist (same explicit-widening pattern as ALLOWED_PERMISSIONS). Fixes #194.

By @thejesh23. Verified: allowlist matches the four negations currently in .gitignore; guard test suite passes locally (17 tests) and in CI.

Closes #194
2026-07-20 18:46:58 +02:00
Oscar Madera 669f5ac1ab docs(setup): add Basic MiKTeX setup instructions for Windows (#186)
Documents the Windows Basic MiKTeX path missing from SETUP.md's minimal-TeX section: enabling silent [MPM]AutoInstall so missing-package installs don't block on a GUI prompt in non-interactive terminals, an mpm pre-install alternative matching the macOS TinyTeX package list, and a PowerShell translation of the existing bash smoke tests. Nested under the LaTeX section as a peer of the TinyTeX/BasicTeX subsection.

By @oscarbol09 (first contribution). Package names, AutoInstall config, and the PowerShell smoke block empirically verified on Windows 11 + MiKTeX.
2026-07-20 18:40:50 +02:00
Lautaro Emanuel Jimenez faa479973a docs(documents): collision-safe postings/ naming + pasted-text trust boundary (#188)
Follow-up to #187: postings/ filenames become <Company> - <Job Title>.txt (collision-free across companies, and /apply gets the company name for free), and the postings/ section gains the untrusted-input reminder - pasted posting text is data to evaluate, never instructions to follow, per SECURITY.md's established rules.

By @Lautaro073.
2026-07-19 20:38:17 +02:00
Ayobami Adegoke 70e0eb43ce fix(cli): reject negative and fractional count/pagination flags in Danish portal CLIs (#191)
Bare z.coerce.number() accepted negative and fractional values for count/pagination flags, and slice(0, limit) with a negative limit silently dropped trailing results instead of erroring. Tightens the schemas to .int().min(1) across all four Danish portal CLIs (including jobnet occupations --per-page) with network-free validation tests.

By @ayobamiseun.
2026-07-19 19:38:48 +02:00
落尘 3a184bc115 fix(jobbank): parse JobPosting entries nested in JSON-LD @graph (#190)
Extracts the JSON-LD JobPosting lookup into a recursive parseJobPostingJsonLd helper that handles top-level objects, arrays, and @graph wrappers (including nested combinations), keeps skipping malformed scripts, and covers all four cases with network-free Bun tests.

By @luochen211. Closes #189.
2026-07-19 19:38:32 +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
Lautaro Emanuel Jimenez 8ac6965170 docs(documents): add postings/ drop folder for bot-blocked job postings (#187)
Manual fallback for postings that block automated fetches: paste the text into documents/postings/<Job Title>.txt. Gitignored so posting text stays local; README documents the convention as a scratch inbox, with applications/<company>_<role>/job_posting.md remaining the archive.
2026-07-19 07:19:26 +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 d2e14418ca docs(setup): document upstream-pull workflow and failure-isolate install loops (#176)
Adds SETUP.md section 8 (pulling upstream updates into a personalized fork: commit-first, check_upstream_updates.py preview, conflicts-as-signal) closing #174, reported by @sharique. Also brings SETUP.md's duplicate install loops up to #157's failure-isolated pattern.
2026-07-17 22:05:43 +02:00
Ayobami Adegoke da2c3bbec9 test(cli): cover Jobindex and Jobnet error contracts (#172)
Extend the offline CLI contract tests to the two remaining Danish
portal CLIs. Both implement the documented error contract (JSON
errors on stderr, exit 1) but had no test locking it in:

- jobindex-search: search without --query, detail without an ID,
  and bunli numeric-option validation (--page not-a-number)
- jobnet-search: detail without an ID, occupations without
  --search-string, suggestions without --query, and numeric-option
  validation

All asserted paths exit before any network request, matching the
no-live-portal-requests CI policy. Assertions were written against
observed CLI output, not assumed shapes.
2026-07-17 21:56:13 +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
Mads LorentzenandClaude Fable 5 dd6d7efea6 docs: warn fork authors about GitHub's default PR base (#167)
Three personalized-fork PRs (#155, #162, #165) were filed against
upstream by accident in one week - GitHub points new fork PRs at the
upstream repo by default, and nothing warned about it at the moment of
filing. Adds a PR template with the heads-up in the compose box (plus
the review norms the process asks for anyway) and one sentence in
CONTRIBUTING's fork section naming the mechanism.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 22:26:45 +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
Mads LorentzenandClaude Fable 5 be427a7607 docs: codify the runtime policy - Claude Code first, runtime forks welcome (#163)
Writes down the architecture decision from the #78 discussion (2026-07-15):
Claude Code is the reference runtime; other agent runtimes are supported at
the edges via the portable portal skills in .agents/skills/, the root
AGENTS.md signpost, and thin-pointer community forks. Per-runtime command
trees stay in forks for the same reason market portals do.

README gets one line in Prerequisites; CONTRIBUTING gets the policy section
beside the market-skills rule it mirrors, including the explicit revisit
conditions.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 22:34:10 +02:00
Jovin Nicholas 1db48568b3 feat(agents-config): add root AGENTS.md thin-pointer specification (#159)
Root AGENTS.md pointer file per the architecture decision in discussion #78: documents both config roots (.agents/skills/ portable portal skills, .claude/ orchestration) and the profile entry points, carries a framework_version marker registered in both version tools.

Design case made by @erikpr1994 in the #78 architecture thread; implementation by @jovin-nicholas.
2026-07-15 22:33:42 +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
Alaa-TaiebandTunic Assistant 55ba1c1652 fix(salary): validate category shape and add --validate preflight (#156)
validate_data() accepted category values that are not {count?, index?}
objects. They slipped through to format_entry(), which then raised
AttributeError on a normal table lookup (or serialized a malformed shape
under --json). It also accepted duplicate company names silently.

- collect_validation_issues() now also flags a non-object category value
  (and non-numeric count / non number-or-string index) as a hard error,
  and duplicate company names as a warning.
- validate_data() keeps its eager-fail behavior (same messages), so
  existing tests and load_data() are unchanged.
- --validate runs the checks standalone and prints an actionable report
  (exit 1 on errors, 0 on warnings-only/clean), letting users pre-flight
  their BYO salary_data.json.

Reproduced on master: validate_data({'companies':[{'company':'Acme',
'categories':{'eng':'not_a_dict'}}]}) returns without error, but
format_entry then raises AttributeError.

Co-authored-by: Tunic Assistant <assistant@tunic.local>
2026-07-15 07:53:01 +02:00
Carlos Mestre CebriánandClaude Fable 5 47118dcbf2 docs(readme): make install loops failure-isolated per iteration (#157)
A failed iteration in the Quick start install loops (e.g. bun missing
from PATH) skipped the cd back to the repo root, so every remaining
tool's cd failed in cascade and the shell ended up stranded inside
.agents/skills/<tool>/cli with nothing else installed. Run each bash
iteration in a subshell and use Push-Location/Pop-Location in
PowerShell so a failure stays contained to its own tool and the loop
always returns to the repo root.


Claude-Session: https://claude.ai/code/session_015EQ2xeixvVdnvbihce3aSt

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 07:52:07 +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
Alaa-Taieb 1417e3cbdf fix(salary): skip non-numeric and identifier columns in Excel conversion (#152)
parse_sheet treated every column that was not company/city as a salary category, with no check that the column actually held numeric salary data. This turned free-text columns (e.g. Notes) into bogus string categories and numeric identifier columns (e.g. Id) into mistaken salary indexes.

- Drop identifier headers (ID_PATTERNS = {id, personnummer}) at classification time.

- Skip non-numeric standalone values and fully-null count/index pairs at row-processing time.

- Adds regression tests (skips_free_text_column, skips_numeric_identifier_column, keeps_numeric_salary_column) that fail on master and pass after the fix.
2026-07-14 20:11:11 +02:00
Alaa-Taieb 4128ca0318 fix(salary): detect company column from header token, not exact match (#151)
convert_salary_excel.py detected the company column via exact membership
in COMPANY_PATTERNS, so common real-world headers like "Company Name" or
"Employer Name" were never matched. parse_sheet then returned [] for that
sheet, silently dropping it from salary_data.json (or exiting with no
output for a single-sheet file).

Route company-column detection through the existing header_matches()
token matcher (already used for count/index detection). This only adds
detections; inputs that already worked (bare "Company"/"Firma"/...) are
unaffected.

Adds a regression test in tests/test_convert_salary_excel.py that fails
on master (returns []) and passes after the fix.
2026-07-14 14:35:45 +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
Ayobami Adegoke 03798ec974 test(cli): cover Jobbank and Jobdanmark contracts (#148)
Jobbank and Jobdanmark each had only one narrow test, leaving required-argument errors, RSS normalization, JSON-LD variants, and malformed-page handling unprotected.

Add network-free fixture and subprocess tests for repeated RSS filters, description and ID parsing, stderr JSON errors, Bunli numeric validation, JSON-LD objects and arrays, optional fields, not-found pages, and parse failures.

The suites now cover eight Jobbank cases and six Jobdanmark cases without making live portal requests.
2026-07-13 20:45:38 +02:00
Yiğit Ö. Ünver fca1c4234e docs(setup): add needspace to the minimal TeX install list (#147) 2026-07-13 20:45:36 +02:00