# career-ops changelog

The latest release of **career-ops** is **v1.32.0**, published September 3, 2026. career-ops is an open-source, local-first AI job-search tool that ships several times a week; this page is generated from the project's GitHub Releases and updates itself.

Updates never touch your data. The system layer (scripts, modes, templates, docs) is replaceable; your layer (`cv.md`, `config/profile.yml`, `data/*`, `reports/*`, `output/*`) is not touched by an update. See the Data Contract: https://career-ops.org/docs

Canonical release history: https://github.com/career-ops-hq/career-ops/releases
HTML version of this page: https://career-ops.org/changelog

---

## career-ops v1.32.0 — September 3, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.32.0

### New

- **discard:** Add discard-log analytics and tuning summary, closes #3392
- **oferta:** Add evidence-tiered requirement importance to Block B
- **plugin:** Add h1b-sponsor for DOL sponsorship history on US roles
- **providers:** Add Built In aggregator provider
- **providers:** Add Feishu Jobs and MokaHR scanner providers
- **providers:** Add Garena careers provider
- **scan:** Add a durable machine-readable scan receipt
- **scan:** Remember permanently dead ATS boards, closes #2840
- **templates:** CJK support for the LaTeX/Overleaf CV path via tectonic, closes #3553
- **updater:** Require confirmation before installation, closes #2865

### Fixed

- **agent-inbox:** Lock resolve rewrites so a concurrent add is never dropped, closes #3478
- **analyze-patterns:** Stop counting Applied as positive
- **calibrate:** Resolve the tracker colmap from the line array, not the header line, closes #3495
- **cv:** Block delegated authorship escalation
- **cv:** Reject CV payloads whose section keys the builders never read, closes #3523
- **dashboard:** Match company slugs at hyphen boundaries so Meta cannot open Metabase's CV
- **data-root:** Resolve templates/states.yml from the codebase, not the data root
- **email:** Align fit points with the tailored CV via the pdf-index row, closes #3460
- **fact-gate:** Count education and training nouns as metrics
- **funnel-velocity:** Fold status-log history into calibration rates, closes #3493
- **gemini:** Resolve encoding mojibake on Windows and route localized modes
- **gitignore:** The tracker's derived SQLite index is unignored outside data/
- **gmail:** Skip page assets so logos stop landing as job leads
- Harden context budget prompt assembly
- **invite-match:** Stop extractReqId backtracking quadratically
- **jd-skill-gap:** Recognize Traditional and Simplified Chinese requirement headings
- **merge-tracker:** A corporate-suffix variant is the same employer, closes #3665
- **merge-tracker:** Two '?' rows with no Via silently merge, overwriting one, closes #3410
- **oferta:** Lead Block B with the decisive columns and freeze their order
- **openai-eval:** Stream the evaluation call instead of a single buffered response
- **openrouter:** Write scan-history and pipeline through the shared locked writers
- **pdf:** Accept the canonical modes/pdf.md tailoring order in validateCvSectionOrder
- **pdf:** Run the fact gate before rendering a CV
- **plugins:** A YAML typo in plugins.yml no longer destroys the whole config, closes #3592
- **portals:** Sweep and validate job_boards, not only tracked_companies
- **reply-matcher:** A partial role-word match must be a whole word
- **reserve-report-num:** Handle --help/-h explicitly
- **role-matcher:** A level stated on both sides of a title must agree
- **scaffolder:** Restore the published npm package name
- **scan-ats-full:** Collapse a Workday requisition served under several sites of one tenant
- **scan-ats-full:** Send the progress counter to stderr under --json
- **scan:** Do not block Dublin OH as Ireland
- **scan:** Resolve scanner user data against the data root, not the cwd, closes #3510
- **set-status:** A transition into Applied seeds the follow-up from set-status itself, closes #3459
- **skill-extract:** Recognize marketing/GTM skills and stop reporting tracked employers as gaps
- **templates:** Honor profile font_family in the Traditional Chinese CV block, closes #3154
- **templates:** Keep CJK fallback fonts active for ja, zh-CN and ko
- **tests:** Answer gitignore probes that cross a user-layer symlink
- **tests:** Stop the photo suite writing cv-preview.html into the user's data root
- **tracker:** Reject malformed application IDs instead of coercing them
- **tracker:** Resolve templates/states.yml from the module, not the cwd, closes #3508
- **tracker:** Resolve the derived index path at call time, not at import
- **tracker:** Sort the applications table by # ascending on write
- **triage:** Fall back to Playwright when WebFetch finds nothing
- **update-system:** Apply() cannot commit when a path it stages is ignored or never tracked
- **update:** Point the updater at the repository's new home
- **updater:** Back up self-bootstrap edits before checkout
- **updater:** Scope commits to shipped files
- **verify-cv-facts:** Bind a count to the nearest metric noun, not the farthest
- **web,dashboard:** Point two user-facing references at the repository's new home
- **web:** A company with no usable slug must not match every tailored CV
- **web:** Enable apply from the generated pdf index
- **web:** Find Grok in its default install dirs, not only on PATH
- **web:** Order the Today action queue before truncating it
- **web:** Preserve a malformed portals.yml instead of overwriting it with the example
- **web:** Rank "Awaiting your decision" by every EVALUATED alias, not an English prefix
- **web:** Require the cv- prefix so the tailored-CV resolvers stop returning the cover letter
- **web:** Resolve the tailored CV for THIS application, not the newest for the company
- **web:** Self-host the production fonts so the app starts without Google
- **web:** Tell the beta reporter's user when the dupe search could not run
- **workday:** Recover offset-clamped tenants with a facet split
- **workday:** Resolve a CXS-form URL as the endpoint, not as a careers page

---

## career-ops v1.31.0 — August 31, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.31.0

### New

- **application-answers:** Read the evaluation's Block H draft answers
- **cv:** Add "text" output_format — tailored markdown CV, no PDF
- **cv:** ATS-friendliness check — score a generated CV for parseability
- External data directory via CAREER_OPS_ROOT — lands #1444
- **hired-wall:** A quiet chevron with the exact remainder says the strip continues
- **hired-wall:** The audit line speaks to the person mid-search
- **hired-wall:** The badge is the title — self-identifying label, own share line, tighter audit line
- **portals:** Profession-agnostic examples in portals.example.yml
- **providers:** JobsDB Hong Kong via the existing jobstreet provider
- **web:** Add a global "Back to Top" button
- Wire reply-watch into status-log.tsv

### Fixed

- **coverage:** Exclude the local-paths declaration file so a fork can commit it
- **cv-sync-check:** Resolve prompt paths from CODE_ROOT instead of an undefined projectRoot
- **cv-sync-check:** Stop allowFail from excusing an uncaught crash
- **dashboard:** Read job URLs from the tracker before the legacy enrichment tiers
- **deps:** Refresh web lockfile for Next update
- **doctor:** Survive a null entry in installed_plugins.json
- **harness:** Make the syntax gate recursive, shared with npm run lint
- **hired-share:** Suggest outcome.mjs's real syntax in the no-Hired-row error
- **providers/icims:** Fill a missing location from the posting's JSON-LD
- **scan:** Level 1 runs Playwright sequentially, matching the shared-session rule
- **scan:** Opt-in word:/stem: anchoring for content_filter keywords
- **tests:** Scope the profile pin and move the last hand-registered suites
- **update-system:** Refuse to run when the install is not its own git toplevel
- **updater:** Parse git status with -z so quoted paths and renames match real files
- **verify-pipeline:** Recover a report number from a link target written with backslashes
- **web:** Honour language.modes_dir and language.output on web-triggered runs
- **web:** Ignore nonfatal CLI stderr on clean run exit
- **web:** Only surface follow-ups that are actually due
- **web:** Regenerate the nested web/ lockfile on postcss bumps
- **web:** Show a cause-specific hint on job failure instead of an always-auth prompt
- **web:** Show Via attribution for confidential employers
- **web:** Stop evaluation tables collapsing to one word per line
- **web:** Stop tracing runtime data paths
- **web:** Tab label spacing and report table word-breaking
- **web:** Use next/link for decision-card report link

---

## career-ops v1.30.0 — August 27, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.30.0

### New

- Add /calibrate — check evaluation scores against real outcomes
- **apply:** Recover previous answers through --read --strict at the CLI seam
- **artifacts:** ATS form copy-paste plain-text exporter
- Clean-markers — audit/strip hidden Unicode from generated text outputs
- **cli:** Add help flags to check-liveness
- **cli:** Application-artifacts.mjs honours --help
- **community:** PR adoption ladder for quiet PRs
- **community:** The Hired Wall, a verifiable ledger of people hired with career-ops
- **cv:** Add optional Interests section to the HTML CV template
- **doctor:** Detect personalization files still carrying template content
- **hired-wall:** Anonymous cards carry sector · location, mirroring the credited-card hierarchy
- **hired-wall:** Verified-hires framing, one CTA line, anonymous silhouette, months parsing
- **outcome:** Add --clean-output to archive-then-remove tailored CVs
- **plugins:** Register career-ops-plugin-xquik (X hiring posts via Xquik, BYO-key)
- **providers/getonbrd:** Scan multiple categories per entry
- **providers:** Add ITviec provider — first Vietnamese job source
- **providers:** Add Torre opportunity-search provider
- **providers:** CareerViet, second Vietnamese job source
- **templates:** Add ATS-friendly single-column CV template pack
- **templates:** Discover template packs by name
- **title-filter:** A stem: prefix, for the half a plain keyword gives away

### Fixed

- **browser-extract:** Read Workday JDs from the CXS API, and fail loudly on an empty JD
- **build-cv-latex:** Render markdown bold in bullets as \textbf
- **calibrate:** Read all 14 outcome spellings outcome.mjs writes
- **cli:** Validate flag values in detect-reposts via the shared safeIntFlag
- **contacto:** Stop searching for contacts the mode never uses
- **cv,pdf:** Address CodeRabbit review feedback for #2213
- **dates:** Weekly-digest and rejection-latency read the local day, not the UTC day
- **doctor:** Surface tracked .bak backup files as a warning
- **fact-gate:** A non-English CV inflating headcount returns 'pass' — say so instead
- **followup:** Stop reporting the candidate's own address as a contact to chase
- **followup:** Stop truncating plus-addressed emails at the plus
- **gfi-claims:** Reply when /assign is used outside the claim pool
- Handle quoted angle brackets while stripping HTML tags
- **hired-wall:** Ellipsize single-line card rows instead of clipping at the viewBox
- **hired-wall:** Honor both permission wordings and default public stories to full credit
- **i18n:** Preserve safety guardrails in localized modes
- **jobbankca:** Count raw entries for pagination
- **merge-tracker:** --backfill-urls rebuilds unfillable rows to full header width
- **outcome:** A tracker edit forks the append-only journal, and directory order decides which half counts
- **pdf:** Enforce workspace boundary in direct renderer
- **pdf:** Run the CLI when reached through a symlink
- **pipeline:** Use browser-first LinkedIn extraction
- **plugins:** Isolate cmdRun results to the requested plugin id
- **plugins:** Scale export hook timeout with tracker size
- **portals.example:** Drop retired EchoJobs block
- **providers:** A16z treats a non-positive total_pages as absent, not as one page
- **providers:** Total_pages outranks a short page in the a16z sweep loop
- **reconcile-pipeline:** Validate CLI flags via the shared helper
- Resolve skill paths from project root
- **salary-gap:** Decide which separator is the decimal point before deleting either
- **scan:** Three scanners stamp first_seen in UTC, the reader measures it in local
- **stats,calibrate:** Ledger-aware funnel + calibration (#1428, #1724)
- **stats,calibrate:** Reject malformed status ledger rows
- **stats:** Stop reporting a neighbouring counter when scan-runs.tsv drifts
- **story-provenance:** Make the context matcher script-aware, not ASCII-only
- **update-system:** Never prune a file the update just preserved, or an asset it references
- **update:** Drop the duplicate prune loop that deleted stale files twice
- **verify:** A check that could not run is a failure, not a warning
- **web:** Classify a tracker-lock filesystem failure as itself, not as contention
- **web:** Key company logos with the Unicode-aware normalizer so non-Latin names don't collide
- **web:** Salvage truncated JSON at each prefix's own depth, not one global pad
- **web:** Stop hiding an employer's whole board after one evaluation
- **web:** Stop killing evaluate runs at 285s and misreporting the kill

---

## career-ops v1.29.0 — August 25, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.29.0

### New

- **anti-fabrication:** Tier the Source-of-Truth boundary and add story-provenance-check
- **contacts:** Linkedin-join.mjs — warm-intro finder from a LinkedIn connections export
- **cv:** Optional url support for projects in HTML and LaTeX
- **detect-reposts:** Skip multi-employer aggregator boards, and say why
- **pdf:** Declare CV section order in config/profile.yml
- **providers:** Add jobbankca, Canada's federal Job Bank
- **providers:** Job.description for recruitee, ashby, and opt-in smartrecruiters
- **providers:** Mycareersfuture, Singapore's national job bank
- **scan:** Add Hacker News scanner with optional AI enhancement
- **scan:** Opt-in anchored keyword matching
- **verify-pipeline:** Flag a malformed follow-ups.md instead of reading it as empty
- **web:** Restrict the local dashboard API to same-origin and loopback

### Fixed

- **add-entry:** Delegate flag validation to shared validateFlags
- **agent-inbox:** Seed the queue under the lock, not before it
- **ashby:** Stop retrying a permanent failure, and honour a clamped Retry-After
- **ashby:** Surface remote work model in the location string
- **batch:** Keep JD word count machine-readable under FORCE_COLOR
- **classify-tier:** Associate + an academic rank is not entry-level
- **cli:** Close the missing-operand hole for value-taking flags
- **cli:** Validate browser-extract flags and honour both value forms
- **cli:** Validate fix-slugs flags
- **cv:** Honor Korean profile font theme
- **dates:** Seven more today-gates resolve in UTC
- **deps:** Update web npm dependencies (major)
- **detect-reposts:** Concurrent openings were reported as reposts
- **followup-cadence:** Validate CLI flags via the shared helper
- **greenhouse:** Populate job.description from the boards API content field
- **liveness:** A dead third-party host must not poison the whole verdict
- **liveness:** Add a LinkedIn rung so LinkedIn postings get a real verdict
- **locks:** One recovery judgment, imported by all three copies
- **locks:** Share the waiting half of the protocol with the three lock copies
- **pdf:** Name both sides when the lexical containment check fires
- **pdf:** Name canonicalization failure separately from a real workspace escape
- **pdf:** Stop the batch-evaluate fixture leaking its tracker env
- **pipeline-lock:** A vanished lock is not a lock you may delete
- **pipeline-lock:** Carry the judged directory's identity to the rm
- **plugins:** Discover a plugin directory that is symlinked into a plugin root
- **portals.example:** Name the support ROLE, not the whole domain
- **providers/lever:** Fold categories.allLocations into location
- **providers:** Retire echojobs, feed is gone behind bot protection
- **providers:** Validate resolved addresses before connecting (DNS rebinding guard)
- **rename-sync:** Migrate 5 vulnerable call sites to renameSyncWithRetry
- **salary-gap:** Treat a period as thousands grouping, not a decimal point
- **set-status:** Resolve --report links from the Notes cell on customized layouts
- **templates:** Extend profile font_family override to ja and zh blocks
- **templates:** Keep a project's tech line with the description above it
- **tests:** Assert flag echo by substring, not by unescaped RegExp
- **tests:** Assert the flag echo with includes instead of an unescaped RegExp
- **tests:** Guard the web/ mirror import behind existsSync
- **tests:** Keep the end of a failing script's output, not just the head
- **tests:** Make the suite symlink-aware for externalized user layers
- **update:** Refuse non-canonical paths in the local declaration file
- **updater:** Parse the SYSTEM_PATHS array instead of scraping it with a regex
- **updater:** Settle same-version SHA mismatch on tree content, not SHAs
- **update:** Stop misreading upstream's own content as a local edit
- **verify-portals:** Decode the HTML entities a board page serves in its title
- **verify-portals:** Stop the slug repair path adopting a stranger's board
- **web:** Give the methodology link a real tap target and a new-tab cue
- **web:** Keep child stderr out of the status response on the crash path
- **web:** Keep the query string in the Explore dedup key so distinct postings don't collapse
- **web:** Keep the run stream alive during silent agent phases
- **web:** Report the uncapped weekly match count while keeping the render bounded
- **web:** Stamp pipeline first_seen with the local day, not UTC

---

## career-ops v1.28.0 — August 20, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.28.0

### New

- **application-answers:** Parse a rendered section back into a snapshot
- **batch:** Native Node.js batch evaluator for Gemini
- **company-history:** Emit no-response-friction signal per RFC #1506 schema v1
- **cv:** Make Work Experience an optional section
- **discover-ats:** Probe the long-tail ATS vendors, not just the big three
- **doctor:** Emit the Gemini Node 20+ verdict instead of leaving it in prose
- **followups:** Export withFollowupsLock so other writers can share the lock
- **oferta:** AI-screening disclosure signal — Block G Signal 15
- **portals:** Add Wellfound search queries to the example template
- **providers:** Add yourator job board provider
- **reports:** Add reports_to to the Machine Summary schema
- **scan-ats-full:** Optional title_filter_full override for the full-board sweep
- **titles:** Let the mode retire a keyword, not only add one

### Fixed

- **add-entry:** Exact-match dedup keys so a prefix cannot false-positive
- **archive-posting:** Reject mistyped flags via the shared helper
- **batch-runner:** Curl pre-fetch JD file to avoid empty $jd_file fallback
- **batch-runner:** Emit the JD word count color-independently
- **classify-tier:** Validate CLI flags and add a usage block
- **consider:** Perform GET /jobs CSRF handshake before the POST
- **contacts:** Add -h/--help and reject unrecognized flags
- **cover-letter:** Preserve --out subdirectories inside the output root
- **cv:** An empty Skills section no longer deletes the sections after it
- **funnel-velocity:** Validate CLI flags via the shared helper
- **intake:** Treat a nonzero version probe as present, not missing
- **liveness:** Read same-origin iframes so embedded postings aren't false negatives
- **locks:** The other two copies of the protocol had the same Windows bug
- **merge-tracker:** Don't let an unscoreable (N/A) re-eval overwrite a real score
- **merge-tracker:** Write rows at the header's actual width on customized trackers
- **pipeline-lock:** An unreadable owner stamp is not an ownerless lock
- **pipeline-lock:** Time out a wedged holder, not an unlucky waiter
- **pipeline:** Serialize browser-backed extraction so workers never share a session
- **portals:** Block German student and support postings in the example title filter
- **portals:** Refresh example board URLs after ATS migrations
- **providers:** Raise inter-page pacing to 250 ms on the eight providers still at 150
- **providers:** Senjob was the fifth private entity decoder, and the weakest — it emitted C0 controls
- **scan:** Add block_hard tier so always_allow cannot discard an explicit block
- **set-status:** Rejects today's date east of UTC, logs tomorrow's west of it
- **stats:** Validate CLI flags via the shared helper
- **test-all:** Tolerate Windows EPERM on fixture cleanup so a green run stays green
- **tracker:** Treat Windows rename contention as contention, not death
- **trust-validator:** A company with an accent in its name is flagged for being on its own domain
- **updater:** Detect tree drift and prune retired system files (#2630, #2532)
- **updater:** Parse git status paths from untrimmed output
- **upskill:** Validate CLI flags via the shared helper
- **verify-cv-facts:** Catch fabricated headcounts outside software
- **verify-portals:** --add probes slugs that cannot exist for an accented company name
- **web:** Block_hard survives the Explore round-trip — type, URL params, seed and serializer
- **web:** Persist the only installed CLI so jobs can start
- **web:** Resolve 27 unmapped states.yml aliases and fix the Turkish status fold
- **web:** Resolve company logos by name, not one guessed domain
- **web:** Safe Codex AI-search exec with mtime-keyed capability cache
- **web:** Stop first-run from claiming no setup
- **web:** Take the core followups lock so web writes cannot race the seeder
- **web:** Today primary action opens the report
- **workspace:** Keep artifacts and PDF metadata in the tracker's workspace

---

## career-ops v1.27.0 — August 18, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.27.0

### New

- **cli:** Add --help / -h usage block to stats.mjs
- **cli:** Shared flag-validation helper, migrate 3 fixed scripts, fix scan.mjs, closes #2775
- **dashboard:** Add a POSTED column showing requisition age
- **dashboard:** Add Stats analytics view with score quality & seniority mix charts
- **intake:** Read documents/ locally to propose source-annotated profile additions
- **invite-match:** Distinguish AI-interviewer platforms from human calls, closes #2673
- **negotiation:** ROI-based salary-negotiation talking points from verified achievements
- **pdf:** Batch render path reusing one Chromium across N CVs
- **providers:** Eightfold AI talent-acquisition boards
- **providers:** Extend getro with auto-resolve, salary, retry and remote-detect, closes #2602
- **providers:** Senjob — the scanner's first African source, Senegal
- **rank:** Opt-in LLM relevance re-ranker for pipeline.md
- **scan:** AND-groups in title_filter.positive
- **scan:** Record failed runs in scan-runs.tsv so trends exclude survivorship bias
- **skill-extract:** Recognize certifications, not just engineering tools
- **templates:** Five named CV templates + a contract test for template variants
- **tracker-sync-check:** Flag applications.md rows that active-interviews.md has already moved past
- **update:** Declare fork-local paths outside the system layer
- **update:** Restore locally deleted system files instead of preserving the deletion
- **web:** Give the Apply page a way back and a way to record that you applied

### Fixed

- **add-entry:** Key non-Latin headings and dedup keys faithfully
- **add-entry:** Reject mistyped flags instead of reading them as data
- **agent-inbox:** Give concurrent add() a burst-sized lock timeout, closes #2824
- **agent-inbox:** Serialize concurrent adds through the shared lock
- **agent-inbox:** Stop dropping requests queued during an add
- **archive:** Key JD captures to report numbers so they resolve after capture day
- **assessment-log:** Reject unknown flags
- **batch-tailor:** --min-score 4.5 was silently ignored, and the worker's pdf mode path was cwd-relative
- **batch:** Retry state updates and lock reservations to prevent worker silent drops
- **build-cv-html:** Render numeric year/date scalars instead of dropping them
- **build-cv-latex:** Numeric year/date scalars are still dropped — #2641 fixed only the HTML mirror
- **check-table-freshness:** Validate CLI flags and add a usage block, closes #2855
- **ci:** Only greet a first-time contributor when it is actually their first
- **ci:** Skip the upgrade leg when nothing managed changed, instead of failing
- **classify-tier:** An incidental senior word outranked an explicit intern marker
- **cli:** A one-letter typo returned a false all-clear at exit 0 in 4 reporting CLIs
- **company-history:** Record response dates via --on, not --note free text
- **contacto:** LinkedIn connection-request limit is 200 on free tier, not 300
- **csod:** Replay bootstrap session cookies and repair fetchResponse()
- **cv-facts:** Treat a k/M/B magnitude suffix as part of the number
- **cv:** Korean and Traditional Chinese CVs had no font rule
- **dedup-tracker:** Reject unrecognized flags instead of silently live-running
- **deps:** Make js-yaml imports work on both 4.x and 5.x
- **deps:** Patch both HIGH advisories in web (js-yaml 4.3.1, nanoid 3.3.18)
- **deps:** Raise the js-yaml floor to ^4.3.1 and guard it
- **doctor:** Detect Playwright MCP servers provided by Claude Code plugins
- **doctor:** Validate CLI flags and add a usage block, closes #2856
- **eval:** Merge openrouter tracker TSVs and send Ollama options to the native /api/chat
- **find:** Print the one USAGE constant on the empty-query path too
- **find:** Reject mistyped flags instead of searching for them
- **followup-cadence:** Parse annotated pins, and add a retire directive
- **followup-seed:** Resolve "today" in local time, not UTC
- **followup:** Ignore an apply date cited about another row, closes #2607
- **invite-match:** Validate CLI flags and add usage
- **jd-similarity:** Add --help and reject unknown flags instead of reading them as paths, closes #2772
- **jd-similarity:** The seniority gate fired on JD boilerplate
- **jd-skill-gap:** Recognize all six markdown heading levels
- **jd-skill-gap:** Recognize sentence-form requirement headings
- **jobvite:** Migrate to the public XML feed after the JSON API retirement
- **keys:** Stop the dotted-I fix from collapsing Polish, Lithuanian and Maltese
- **keys:** Stop the Turkish dotted capital from splitting one employer in two
- **liveness:** Classify a rate-limited posting as uncertain, not expired
- **liveness:** Treat an api.lever.co 404 as inconclusive, not proof of removal
- **locks:** Treat Windows rm/stat contention as contention, not death
- **match-star:** Score a non-English story bank against a question in its own language
- **merge-tracker:** Deterministic dedup on the posting URL
- **merge-tracker:** Match inline URL headers so URL dedup activates for doc-style rows
- **merge-tracker:** Re-eval no longer inherits the superseded report's checkmark
- **ollama:** Include profile context in evaluations
- **oraclecloud:** Accept Oracle's numbered tenant apexes (oraclecloud1-99.com)
- **patterns:** Rate denominators counted rows that could never contribute
- **pipeline-lock:** A non-EEXIST mkdir refusal is contention, not a fatal error, closes #2777
- **pipeline-lock:** Jitter the retry so a starved writer costs a retry, not an item
- **plugin-audit:** Validate CLI arguments and add a usage block, closes #2771
- **process-quality:** Three undisclosed employers were reported as disclosed
- **providers:** Decode HTML entities in titles before matching so encoded ampersands stop dropping jobs
- **providers:** Fill in the docs and vendor-detection gaps
- **providers:** Harden workable with retry, headers, and serialization
- **providers:** IsRetryableError no longer misclassifies a refused redirect
- **providers:** Make themuse resilient to per-page fetch failures
- **providers:** Personio falls back to HTML scrape when the XML feed is disabled
- **providers:** Phenom pagination/retry fixes, drop dead auto-detection
- **providers:** Reject numeric entities outside the XML 1.0 Char set
- **providers:** Route seven RSS decoders through the shared safe entity decoder, closes #2790
- **providers:** Route softgarden and radancy through the shared entity decoder
- **reply-matcher:** Require corroboration for partial role-title matches
- **reply-matcher:** The '?' unknown-employer placeholder matched everything
- **reply-watch:** Reject unrecognized CLI flags instead of treating them as a path
- **reserve-report-num:** Do not read a bare date file as a report number, closes #2757
- **role-matcher:** Tokenize non-Latin role titles and scope the mark fold, closes #2781
- **scan-ats-full:** Cap single-host ATS sweeps at their own concurrency
- **scan:** Locate the URL anywhere in a pipeline.md entry
- **scan:** Take the shared lock for scan-history.tsv appends
- **set-status:** Stop the --role guard from folding every non-Latin title to the same key
- **states:** Aliases the engine accepts were missing from states.yml
- **status:** Fold Turkish dotted capitals so uppercase tracker rows resolve to canonical states
- **tests:** Contain a discovered suite that throws at import
- **theme-style:** A `$`-pattern in a profile style value splices the body into the CV's
- **update:** Commit the index so file-mode changes are not dropped
- **update:** Ignore CRLF/LF-only differences when detecting local system edits, closes #2817
- **update:** Reconcile .gitignore on apply so new ignore rules reach existing installs
- **velocity:** Count a web-written transition as an observation
- **verify-cv-facts:** Fold allow_metrics through the same noun synonyms
- **verify-cv-facts:** Treat a period as a thousands separator too
- **voice-dna:** Make voice-dna.md private via the template pattern
- **web:** Add Grok Build CLI to the web runtime picker
- **web:** Analytics tells an offer-holder they have 0 interviews (and nudges them to try harder)
- **web:** Anchor the fallback stderr classifier so a word can't fail a run
- **web:** Carry the posted: segment so web evaluations reach the POSTED column
- **web:** Detect OpenCode in its default install directory
- **web:** Emit the posting URL in the tracker-additions TSV so web runs join the dedup
- **web:** Enforce that no runtime grants itself blanket write permission
- **web:** Finish Unicode company keys for explore + registry
- **web:** Fold Turkish dotted capitals in status keys, and stop hand-copying the state list
- **web:** Keep Codex JSONL + exit code authoritative for web scoring
- **web:** Let the pipeline table scroll horizontally instead of clipping on narrow screens
- **web:** Make the tracker reader agree with parseTrackerRow on row shape
- **web:** Prevent Codex run jobs from waiting on stdin
- **web:** Read target_roles with the shape it is actually written in
- **web:** Show the retry card, not the update-checkout panel, for runtime scan errors
- **web:** Skip {n}-RESERVED.md sentinels when looking up reports
- **web:** Stop the 16-chip cap from truncating the user's own portals.yml
- **web:** Strip the author letter from any lettered block, not just A-G
- **web:** Take the core tracker lock in POST /api/status
- **workday:** Key the cap-hit warning on entry provenance, not on --since

### Faster

- **analytics:** Drop redundant existsSync probes before report reads
- **scan:** Read each dedup source once per scan run

### New

- **web:** Give the Apply page a way back and a way to record that you applied

### Fixed

- **deps:** Make js-yaml imports work on both 4.x and 5.x
- **deps:** Patch both HIGH advisories in web (js-yaml 4.3.1, nanoid 3.3.18)
- **deps:** Raise the js-yaml floor to ^4.3.1 and guard it
- **keys:** Stop the dotted-I fix from collapsing Polish, Lithuanian and Maltese
- **keys:** Stop the Turkish dotted capital from splitting one employer in two
- **scan:** Take the shared lock for scan-history.tsv appends
- **web:** Add Grok Build CLI to the web runtime picker
- **web:** Analytics tells an offer-holder they have 0 interviews (and nudges them to try harder)
- **web:** Anchor the fallback stderr classifier so a word can't fail a run
- **web:** Carry the posted: segment so web evaluations reach the POSTED column
- **web:** Detect OpenCode in its default install directory
- **web:** Emit the posting URL in the tracker-additions TSV so web runs join the dedup
- **web:** Enforce that no runtime grants itself blanket write permission
- **web:** Finish Unicode company keys for explore + registry
- **web:** Fold Turkish dotted capitals in status keys, and stop hand-copying the state list
- **web:** Keep Codex JSONL + exit code authoritative for web scoring
- **web:** Let the pipeline table scroll horizontally instead of clipping on narrow screens
- **web:** Make the tracker reader agree with parseTrackerRow on row shape
- **web:** Prevent Codex run jobs from waiting on stdin
- **web:** Read target_roles with the shape it is actually written in
- **web:** Show the retry card, not the update-checkout panel, for runtime scan errors
- **web:** Skip {n}-RESERVED.md sentinels when looking up reports
- **web:** Stop the 16-chip cap from truncating the user's own portals.yml
- **web:** Strip the author letter from any lettered block, not just A-G
- **web:** Take the core tracker lock in POST /api/status

---

## career-ops v1.26.0 — August 10, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.26.0

### New

- **cover:** Optional letter.signature sign-off block, closes #2510
- **cv:** Strip the Core Competencies section when it has no entries, closes #2509
- **doctor:** Warn when an API key is overriding the Claude subscription
- **followup:** Rejection-latency signal, courtesy-threshold only
- **invite-match:** Recognize rejection emails, not just interview invites
- **oferta:** Minimum-wage lawyer question, arithmetic-only
- **oferta:** Pay-transparency range-width check, self-computed only
- **offer-prep:** Sub-statutory terms route to lawyer, no floors table
- **providers:** Add join.com SSR __NEXT_DATA__ parser
- **providers:** Add remotli — Swiss remote job board
- **scan:** Add getro, consider and joinup zero-auth providers
- **scan:** Make the pipeline and scan-history paths env-overridable, closes #2271

### Fixed

- **a16z-speedrun:** Retry a transient page failure instead of aborting the whole board
- **arbeitsagentur:** Move to the v6 Jobsuche API
- **cover:** Fail on an unresolved template token instead of shipping it into the letter
- **cv:** Drop empty Skills section instead of rendering a bare header
- **cv:** Pair _EMPTY fallbacks with their block so mixed rows stay aligned
- **cv:** Pass a replacer function so $-patterns in candidate text stay literal
- **dedup:** Use the shared Unicode-aware company key instead of a local ASCII copy
- **dedup:** Write the blind-via key prefix as an escape, not a raw NUL byte
- **doctor:** Treat the cloud billing switches as opt-in, not merely present, closes #2592
- Extend the backup-file ignore to .npmignore and pin it with a test
- **http:** Consolidate duplicated User-Agent strings into a shared module
- **invite-match:** Non-Latin company names returned zero candidates
- **jd-skill-gap:** CRLF-terminated JDs extracted zero skills
- **merge-tracker:** Write a lower-scored re-evaluation through instead of discarding it
- **providers:** Hackernews thread lookup filters by the whoishiring tag
- **providers:** Move The Hub to the v2 jobsandfeatured API
- **providers:** Resize a16z page budgets for the 50-job page size
- **repo:** Add .gitattributes so Windows checkouts stop producing whole-file diffs
- **scan:** Read --company/--posted-after/--posted-before in =value form too
- **scan:** Unicode-aware keys for role dedup and company matching
- **seeds:** Walk every YC page instead of stopping after page 1, closes #2525
- **seeds:** Walk every YC page instead of stopping after page 1
- **update-system:** Apply .gitattributes before the files it governs
- **web:** Derive company matching keys from the core, not an ASCII-only copy
- **web:** Re-read states.yml when it changes instead of caching it for the process lifetime
- **web:** Route Today's "See all N" link to the fresh-matches view
- **web:** Take Write/Edit away from the dashboard's pdf mode

---

## career-ops v1.25.0 — August 4, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.25.0

### New

- Add company:funded — review-first funded-company discovery from public RSS sources
- **cv:** Add optional Awards / Honors section to HTML + LaTeX CV templates
- **i18n:** Add Italian translations for interview modes
- **pdf:** Opt-in hiring-manager audit pass — adversarial, research-grounded CV review
- **providers:** GetManfred board-wide feed (Spanish/EU tech, salaries published)
- **reposts:** Add --help usage block to detect-reposts
- **scan:** Add --since, a relative posted-date bound that also stops paging early
- **test:** Dynamic upgrade PR gate — old install applies the commit under test
- **web:** Follow-up Tracker page with logging, history, and cadence settings
- **weekly-digest:** Weekly interview digest aggregator

### Fixed

- **batch-runner:** Tab-IFS collapse recorded score "-" for every successful offer
- **cli:** --flag=value was silently ignored in five scripts
- **company-funded:** Catch the layoff spellings #2404 left through
- **company-funded:** The cuts N% layoff exclusion never matched
- **company-history:** Count a hire as a company response
- **cv-facts:** The anti-fabrication gate saw nothing in five shipped locales
- **cv-facts:** The employer/title gate only matched lowercase triggers
- **cv-latex:** Single mailto: scheme + escape OT1-unsafe glyphs
- **dashboard:** Localize the hired status label and buffer split stream openers
- **dashboard:** The progress funnel dropped Hired from every stage
- **dedup:** Use Unicode-aware Via key for blind-employer grouping
- **deps:** Update npm dependencies (+ Dockerfile playwright pins, web lockfile sync)
- **doctor:** Detect Playwright MCP in JSONC OpenCode configs
- **fingerprint:** Non-Latin employers no longer collapse to the same company key
- Git-ignore article-digest.md and guard the user layer against tracking
- **jd-skill-gap:** Warn when zero skills are classified instead of printing a clean summary
- **liveness:** Classify transient 5xx as uncertain, not expired
- **merge-tracker:** Collapse placeholder Notes to the re-eval marker
- **merge-tracker:** Four silent data-loss paths in the merge loop, with Unicode-safe company matching
- **providers:** A16z speedrun feed silently truncates to 50 jobs
- **providers:** Dead boards throw instead of reading as live-but-empty
- Resolve the PDF manifest from the tracker, not the install dir
- **scan-ats-full:** --since -5 silently scanned nothing and called it a clean run
- **scan-ats-full:** Keep the checkpoint when a resolver outage stops a sweep
- **set-status:** Close the report-less-row blind spot and add explicit --row/--report selectors
- Show actual LinkedIn URL as cover-letter link text, not the word "LinkedIn"
- Strip GitHub URL scheme case-insensitively in cover letters
- **sync-pdf:** Reject unknown options
- **tailor:** Prompt-cache the openai-tailor.mjs static system prefix
- **test-all:** The states.yml freeze guarded 6 of 9 ids
- **test:** Forbid finish() in discovered suites — it forges the global verdict
- **test:** Report the Windows-unobservable lock contention signal instead of failing
- **tests:** Budget harness waits for slow runners and preserve crashed-script diagnostics
- **tests:** Find Git Bash under Scoop and name the fallback shell in failures
- **tests:** Isolate tracker-columns fixtures from the real reports/ directory
- **test:** Stop test-all silently passing when a node:test suite fails
- **test:** Use a junction on Windows for the node_modules link
- **tracker:** Non-Latin company names all keyed to empty string, silently overwriting each other
- **tracker:** Rank Hired in dedup, canonicalize its aliases, stop bare tracker-number merges across roles
- **verify-cv-facts:** Stop the modifier count from deciding whether a claim exists
- **verify-pipeline:** Orphan check masks and invents orphans
- **verify-pipeline:** Unicode-aware keys for company/role/Via grouping
- **web:** Render PDFs from the backend instead of the spawned agent
- **weekly-digest:** --from=… was silently ignored, digesting the wrong week
- **workable:** Use the public widget API so large accounts are scanned

### Faster

- **detect-reposts:** Bucket titles instead of comparing every pair
- **fingerprint:** Popcount table + hoisted loop state in findCrossListings
- **tests:** Fix the dead SSRF DNS stub and cut three fixed costs from test-all

---

## career-ops v1.24.0 — July 30, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.24.0

### New

- **compliance:** Check-table-freshness.mjs — staleness validator for jurisdiction tables (closes #2036)
- **contacts:** Company-history.mjs, employer silence and repost facts, closes #1711
- **contacts:** Job-search phonebook with vCard export
- **cv:** Block unsupported employer, title, and tool claims in cover letters
- **cv:** Recommend CV reuse for similar JDs — deterministic jd-similarity helper
- **doctor:** CLI-aware Playwright MCP detection + OpenCode config support
- **interview-prep:** Detect call platform (phone/Zoom/Teams/Meet) from invite text
- **interview-redflag:** Protected-grounds question detection with jurisdiction table
- **interview/debrief:** Correct contradicted facts in the prep file in place
- **interview/plan:** Wire in interview-prep's sourced-question research before Block 4
- **interview:** Debrief directly from an existing transcript
- **ledger:** Set-status transition log + mode wiring
- **oferta/apply:** Immigration-status requirement overreach — jurisdiction table + posting signal + form warning
- **oferta/apply:** Jurisdiction-prohibited content signal — table + Block G + apply-form warning
- **oferta:** Agency licensing check — jurisdiction table + registry pointer for agency-mediated postings (closes #2037)
- **offer-prep:** Jurisdiction-aware restrictive-covenant notes
- **outcome:** Outcome recording and application archiving, phase 1
- **portals:** Standalone fix-slugs.mjs — auto-write ATS slug fixes to portals.yml
- **providers:** A16z speedrun talent network board-wide feed
- **providers:** Add iCIMS to the reverse-ATS full sweep, closes #2137
- **providers:** Cryptocurrencyjobs — Web3 job board via public RSS, zero-auth
- **radancy:** Support legacy TalentBrew markup and the JSON results fragment
- **scan-history:** Additive normalized-company column for reliable repost keying
- **scan:** Add Playwright scanner for Interamt.de
- **scan:** Country-eligibility filter for remote postings, closes #2093
- **scan:** Negative-cache resolver refusals and stop the sweep on a resolver outage
- **scan:** Pace DNS lookups so a full sweep can't trip a resolver's rate limit
- **stats:** Wire followup-cadence's cold classification into activeApps
- **test:** Seed-fixture.mjs + era-appropriate install fixtures
- **triage:** Standalone two-pass triage mode to cut evaluation tokens
- Versioned application CV artifact schema — bundles with flat-path coexistence

### Fixed

- **analyze-patterns:** Use symbol-safe lookarounds so C++, C# and .NET stop vanishing from tech gaps
- **batch:** Don't mark a worker-reported failure as completed
- **ci:** A fork signature's valid check must not go red when the ack comment can't post
- **cover:** Prevent double comma in achievement bullets
- **dashboard:** Pin the row's own status to the top of the status picker
- **dashboard:** Stop reading funding and valuation figures as Pay
- **dashboard:** The 'hired' status renders with no colour in the pipeline list
- **echojobs:** Keep hybrid roles distinguishable from remote
- **followup-seed:** Age out an orphaned recover guard so it can't permanently disable stale-lock recovery
- **followup:** Accept an estimated apply date and report which date the age came from
- **followup:** Recognise name-only outreach so an empty contact list means no contact
- **jd-skill-gap:** Canonicalize CV and JD tokens through skill-extract
- **merge-tracker:** Stop sibling-req clobber when the qualifier is a slashed acronym
- **pipeline-lock:** Floor the ownerless stale check, harden the timeout tests
- **role-matcher:** Fold accented Latin letters before tokenizing role titles, closes #2207
- **scaffolder:** Materialize the Kimi skill entrypoint on symlink-less filesystems
- **scan:** A title-stated remote role survives a city-only location
- **scan:** Ignore StepStone's volatile rltr param when deduping posting URLs
- **scan:** Portal-health cross-process lock + resolve PORTAL_HEALTH_PATH from cwd
- **scan:** Record auth, server and unknown fetch errors in portal-health instead of reachable
- **test:** Accept a materialized skill entrypoint, not just a symlink
- **tracker-utils, followup-seed:** Floor the ownerless stale-lock check so a just-created lock can't be stolen
- **tracker:** A data row containing --- is invisible to merge-tracker dedup
- **web:** Add Hired to the states.ts FALLBACK so the degraded path accepts it
- **web:** Label-aware pipeline.md reader — posted:/trust:/note: never misread as columns
- **web:** Propagate the Hired terminal-success state across the whole dashboard

---

## career-ops v1.23.0 — July 28, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.23.0

### New

- **batch:** Add batch-tailor.mjs for bulk CV tailoring of high-scoring roles, closes #1836
- **cli:** Add Cursor as a supported CLI host
- **cv:** Support declarative section HTML partials from disk, closes #2183
- **dashboard:** Add a RESPONDED filter tab to the pipeline screen, closes #1999
- **dashboard:** Build the pay-span regex from an explicit currency list
- **eval:** Token-budget-aware context compression for the Gemini and OpenAI evaluators, closes #2077
- **i18n:** Add Dutch (nl) modes
- **latex-tex:** Support resumeItemWithoutTitle bullets and resumeSubItem skills
- **oferta:** Flag explicit no-sponsorship as a hard blocker, closes #2016
- **pdf:** Dynamic CSS theming from a profile.yml style block, closes #1837
- **providers:** Add Flowxtra board-wide job provider
- **providers:** Add VDAB zero-auth provider
- **scan:** Discover-ats.mjs — resolve a company list to scannable ATS boards, closes #1864
- **skills:** Extract the shared skill tokenizer into skill-extract.mjs
- **upskill:** Learning plan on the aggregate gap heatmap, closes #1740

### Fixed

- **agentic-jobs:** Replace the broken HTML scraper with the documented REST API, closes #2143
- **agents:** Compress AGENTS.md ~20% with zero semantic loss
- **batch:** Update Claude tier mapping to the current model lineup
- **cv:** Add GitHub contact link to the HTML CV pipeline
- **dashboard:** Persist status changes from viewer Discard/SKIP and stale rows
- **dedup:** Merge notes from removed entries instead of dropping them, closes #1833
- **deps:** Update dependency next to v16.2.11 [security]
- **deps:** Update module golang.org/x/sys to v0.47.0
- **doctor:** Treat missing user prerequisites as non-fatal setup warnings
- **funnel-velocity:** Count non-canonical Applied rows in the waiting backlog
- **jd-skill-gap:** Recognize real-world requirement headers, stop at benefits
- **liveness:** Normalize typographic punctuation and accents before matching, closes #2162
- **liveness:** Treat the SPA "has been filled" phrasing as expired, closes #2211
- **match-star:** Tokenize tag matches so short tokens do not substring-collide
- **normalize-statuses:** Use header-mapped columns instead of fixed indices
- **opencode:** Add missing /career-ops command router, closes #2191
- **patterns:** Recognize the canonical Hired status in analyze-patterns
- **pdf:** Apply the CV section-order check to non-English CVs
- **pipeline:** Lock appendToPipeline() against concurrent read-modify-write, closes #2188
- **plugin:** Mirror plugin.json at .github/plugin/ for the awesome-copilot validator
- **providers:** Guard decodeEntities against out-of-range code points
- **reply-matcher:** Decide rejections before offers to stop mis-typing
- **reply-matcher:** Sanitize domains harvested from tracker notes, closes #2134
- Restore empty-Certifications section stripping lost in the v1.22.0 refactor, closes #2118
- Restore the Kimi SKILL.md entrypoint as a real symlink
- **role-matcher:** Stop collapsing leveled variants into the bare title
- **role-matcher:** Strip the "Member of Technical Staff" prefix before tokenizing, closes #1965
- **role-matcher:** Treat 'product' as a baseline token so PM sibling specialties stay distinct
- **salary-gap:** Parse US ranges with the currency symbol on both bounds, closes #2190
- **scan:** Keep body consumption inside the fetch timeout window, closes #2136
- **scan:** Lowercase the path in normalizeUrlForDedup to catch cross-source duplicates
- **scan:** Recover Greenhouse office cities when location.name is a work model
- **scan:** Word-boundary location keywords + URL location fallback
- **set-status:** Fail closed when a lone company row contradicts --role, closes #2009
- **skill:** Routing drift + discoverable metadata across the agent surface
- **successfactors:** Preserve brand path for multi-brand RMK tenants, closes #2010
- Sync the PDF and scan summary output
- **test:** Make the SYSTEM_PATHS coverage guard actually run in CI
- **tracker:** Set SQLite busy_timeout to avoid SQLITE_BUSY under concurrent access, closes #1957
- **tracker:** Sync the PDF column from data/pdf-index.tsv, closes #1429
- **update-system:** Rollback removes additions left behind under a directory pathspec, closes #2015
- **update-system:** Verify the target manifest materialized instead of reporting success, closes #1998
- **verify-cv-facts:** Detect metric claims the pattern was silently missing
- **web:** Resolve nested postcss and sharp advisories via overrides

### Faster

- **scan:** Cache DNS lookups in-process to stop resolver floods, closes #2154

---

## career-ops v1.22.0 — July 21, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.22.0

### New

- **cv:** Chinese minimal CV template
- **cv:** Improve profile-photo configuration and preview
- **expand:** Add /expand mode to discover missing CV competencies, closes #1521
- **gemini-eval:** Static prefix as systemInstruction for implicit caching
- **i18n:** Simplified Chinese interview modes (plan/practice/debrief)
- **oferta:** Block G signal 9 — third-party platform location tag mismatch
- **openai-eval:** Host-gated prompt-cache breakpoint for the static prefix
- **pdf:** First-class zh-CN typography support
- **pdf:** Warn on rendered page overflow, --strict-pages opt-in hard limit
- **pipeline:** Per-step token breakdown at end of run
- **providers:** Add Alibaba Group careers provider
- **providers:** Add Gem scanner provider
- **providers:** Add Jobvite zero-auth provider
- **providers:** Add Welcome to the Jungle and Agentic Engineering Jobs providers
- **scan:** --posted-after/--posted-before absolute posting-date filters
- **scan:** Add visa_filter for surfacing visa-sponsoring roles, closes #2057

### Fixed

- **arbeitsagentur:** Only tag nationwide-remote when homeofficetyp is VOLLSTAENDIG
- Drop stale plugins-registry.json from updater path manifests
- Honor output language in headless evaluators
- **ollama-eval:** Move temperature into options where Ollama reads it
- **pdf:** Drop empty Education section instead of rendering a bare header
- **pdf:** Harden optional-section boundaries, extract shared cv-sections-core
- **pipeline:** Recognize #1799 score sentinels in verify-pipeline
- **providers:** Build public SmartRecruiters URLs without /postings/
- **scan-ats-full:** Wire content_filter into reverse scans
- **scan:** Hide unconfigured salary/content filter summaries
- **scan:** Normalize cosmetic URL variants for dedup
- **scan:** Seed the company+role dedupe key from all three seen-sources
- Serialize all tracker writers through the shared lock
- **test:** Keep discovered suites from terminating the run mid-suite
- **tracker:** Recognize the Hired canonical status in merge-tracker and stats
- **verify-cv-facts:** Stop a bare '<' from swallowing source evidence

---

## career-ops v1.21.0 — July 17, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.21.0

### New

- **apply:** Warn before reapplying to a repeat company, closes #1920
- **cover-letter:** Support --format and --report CLI arguments, closes #1828
- **dashboard:** Manifesto shortcut, help entry and footer link
- **email:** Add confirmed-time no-show follow-up variant, closes #1859
- **interview-prep:** Cross-reference coffee chat notes against interview intel, closes #1940
- **interview-redflag:** Flag scope/compensation mismatch as a separate fifth dimension, closes #1942
- **interview-redflag:** Suggest a ready-to-copy blacklist row at Reconsider level, closes #1854
- **interview/plan:** Wire the existing Panel Intel table into multi-panelist rounds, closes #1857
- **ledger:** Immutable n: ordinal per signature + first-contribution question for new signers
- **liveness:** Add Workday to the zero-token ATS liveness API rung
- **oferta:** Block G signal for benefits terminology country mismatch, closes #1935
- **onboarding:** Route to the manifesto from the README and the first successful scan
- **pdf:** Add role name to the tailored PDF filename for multi-role tracking
- **providers:** Add Oracle Recruiting Cloud zero-auth provider
- **reports:** Unified Risk Summary block joining the five company-risk signals, closes #1737
- **salary-gap:** Track compensation figures stated per interview round, closes #1852
- **scan:** Persist and surface the scanner trust signal to pipeline and history, closes #1743
- **scan:** Track portal health over time and surface coverage decay, closes #1744

### Fixed

- **cv-template:** Keep role header with its bullets when a location line is present
- **doctor:** Warn when Node is below 22.5 instead of passing silently, closes #1829
- **envrc:** Only load the Nix flake when Nix is installed
- **interview-redflag:** Drop GitHub PR references from candidate-facing output templates
- **ledger-bot:** Make the first-contribution confirmation copy match what ships
- **ledger-bot:** Restore the 'done' confirmation now that the certificate render is live
- **onboarding:** Relay the manifesto note in agent-mediated updates
- **pdf:** Drop empty Projects section instead of rendering a bare header
- **role-matcher:** Keep base titles distinct from specialized-suffix siblings, closes #1881
- **scan-ats-full:** Respect data/blacklist.md, closes #1911
- **scan:** Quiet dotenv so --json stdout stays parseable, closes #1906
- Unify atomic report number allocation, closes #1733 #1801 #1835 #1895
- **update-system:** Attribute the post-update manifesto link (utm_source=updater)
- **update-system:** Surface commit failures instead of silently swallowing them, closes #1844
- **upskill:** Keep fetched JD text as a string in the targeted --url-text path, closes #1894

---

## career-ops v1.20.0 — July 14, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.20.0

### New

- **onboarding:** Surface the CareerOps Manifesto after updates and setup

### Fixed

- **upskill:** Correct inverted known-skill suppression in targeted mode

---

## career-ops v1.19.0 — July 13, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.19.0

### New

- **batch:** Apply compensation reliability checks to batch workers
- **browser-extract:** Configurable jd-mode text cap via --max-chars
- **calibration:** Funnel-velocity.mjs — own funnel vs market benchmarks (#1693, part 1)
- **cv:** Fact validator — block invented metrics in generated CVs
- **dashboard:** Add Spanish (es) locale to the i18n catalog, closes #1764
- **dashboard:** Discard reason picker with learning loop, closes #1380
- **dashboard:** I18n extraction for the Go TUI (split from #1666)
- **email:** ATS-broken fallback scenario — recover a stuck application
- **i18n:** Add Traditional Chinese (zh-TW) modes
- **i18n:** Chinese salary reliability checks — zh mirror of the compensation tiers
- **img-to-pdf:** Convert a screenshot into a PDF for ATS uploads
- **interview-prep:** URL entry path for a role that was never evaluated, closes #1816
- **invite-match:** Fuzzy-match interview-invite emails to tracker entries
- Language.output preference — output language decoupled from market modes
- **latex:** Opt-in latex-tex in-place tailoring of user-owned .tex CVs
- Make CLAUDE.md a thin @AGENTS.md wrapper — single canonical agent spec
- **modes:** Wire the opt-in CLI extractor into the oferta + auto-pipeline JD paths
- **oferta:** Block G signal 7 — AI-buzzword vs infrastructure mismatch
- **oferta:** Company-type + compensation-reliability tiers in evaluations
- **openrouter:** Prompt-cache the static system prefix via cache_control
- **pdf:** Deterministic HTML CV renderer to cut PDF-step token burn
- **pdf:** JD skill-gap checker before CV generation, zero-LLM, closes #1558
- **pipeline:** Openai-tailor.mjs — headless CV tailoring for the no-agent path
- **providers:** 4dayweek auto-detect with spoof-safe host matching (#1657 follow-up)
- **providers:** Add LaraJobs zero-auth RSS provider
- **providers:** Add Meituan careers provider
- **providers:** Add Tencent careers zero-auth provider
- **registry:** Register serper and theirstack provider plugins (#1645, #1697)
- **registry:** Split plugins-registry.json into per-plugin files
- **reply-watch:** Paste-reply.mjs manual input path for users without Gmail, closes #1802
- **scan:** Data/blacklist.md — user-owned do-not-apply list
- **scan:** Opt-in max_posting_age_days freshness gate
- **scan:** Persist provider postedAt to pipeline and scan-history
- **scan:** Suppress duplicate roles via company aliases
- Spend_tier config for model-agnostic model routing + batch pre-screen gate
- **templates:** Config-selectable CV and cover-letter templates
- **tracker:** Assessment-log.mjs — skills-assessment events with staleness signal
- **tracker:** Recognize "—"/"-" as valid score-cell N/A sentinels, closes #1799
- **upskill:** Aggregate skill-gap analysis from tracked reports (#1520, phase 1)
- **upskill:** Targeted  single-posting mode with SSRF-guarded fetch, closes #1739

### Fixed

- **analyzer:** Resolve report paths relative to the tracker file
- **cover:** Resolve the cover-letter template through the shared resolver
- Gitignore data/active-interviews.md (user interview data)
- Ignore logo cache directory
- **liveness:** Treat off-posting redirects as uncertain, not active
- Load profile and custom rules deterministically in all mode paths
- **merge-tracker:** Preserve empty interior cells in tracker-row parsing
- **pdf:** Clean temporary HTML when browser launch fails
- **pdf:** Render markdown bold in CV summary as  (ATS-safe)
- **portals:** Validator accepts provider-plugin ids like the runtime does
- **providers:** Support explicit api: override for ashby/lever with host-pinned guards
- **reserve-report-num:** Support report numbers past 999
- **scan:** Restore browser-like headers on Workday CXS requests, closes #1811
- **templates:** Allowlist the template format to prevent path traversal
- **test:** Normalize CRLF at read time for doc assertions
- **tests:** Close CodeQL command-injection findings in the test harness
- **tests:** Skip web-reader tests when web/ is absent on core-only installs
- **tracker:** Detect and prevent duplicate tracker # numbers
- **update-system:** Add .claude/hooks/ to USER_PATHS
- **update-system:** Keep the updater self-loading to survive old→new re-exec

---

## career-ops v1.18.0 — July 7, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.18.0

### New

- Classify application replies and generate review digest, closes #1585
- **dashboard:** Hired canonical status + job-landed celebration flow
- **i18n:** Add German translations of interview modes
- **i18n:** Add Hindi language support with dedicated modes and documentation
- **i18n:** Add Indonesian (id) modes
- **interview-prep:** Structured Panel Intel table
- **interview:** Company red-flag detector from transcript signal
- Match employer reply candidates to application tracker, closes #1584
- **oferta:** Employment classification risk signal in Block G, closes #1630
- Offer-prep mode — contract reading companion at Offer stage, closes #1608
- **offer-prep:** Negotiation reply-draft step — draft-only email from Items to raise
- **patterns:** Per-agency advance-rate analysis from the Via channel
- **pdf:** Allow intentional CV section reordering via --allow-reorder
- **providers:** Add Lever EU instance (jobs.eu.lever.co)
- **providers:** Cornerstone OnDemand (csod) + Rheinmetall
- **providers:** EchoJobs zero-auth JSON aggregator provider
- **providers:** Phenom People (phenom) + Radancy TalentBrew (radancy)
- **providers:** TKMS, Heckler & Koch, and Deutsche Bahn providers
- **registry:** Add career-ops-plugin-markdown v0.1.0
- **salary:** Mode wiring — advertised_comp + observation appends
- **salary:** Salary-gap.mjs — append-only compensation observation analyzer
- **scan:** JD-content fingerprinting — flag agency+direct cross-listings, closes #1597
- **scan:** Opt-in CLI page extractor (browser-extract.mjs)
- **scan:** Persist per-run scan counters to data/scan-runs.tsv
- **scan:** Scope content_filter per title-category via by_title_keyword
- **scoring:** Culture-screen dimension with structural capping
- **stats:** Lifetime pipeline stats aggregator
- Titles mode — suggest adjacent job titles from the CV, closes #1632
- **tracker:** Set-status.mjs — canonical CLI for tracker status updates, closes #1428
- **tracker:** Via channel — end employer vs recruiter/agency intermediary

### Fixed

- **dashboard:** Duplicate tracker headers resolve last-occurrence to match JS reader
- **deps:** Update npm dependencies
- **modes:** Enforce modes/_custom.md overrides across mode files, closes #1618
- **modes:** Wire modes/_custom.md into the read path — house rules were write-only
- **pdf:** Explain the agentic HTML flow in generate-pdf.mjs usage output
- Prevent role-matcher from collapsing different seniorities, closes #1616
- **scan-ats-full:** Reject unrecognized CLI flags with usage help
- **tracker:** Retrofit remaining positional readers onto the shared header-aware parser
- **update:** Raise local git timeout default 30s -> 120s for cloud-evicted trees
- **web:** 44px tap-targets at the component level
- **web:** Contrast tokens — AA across both themes
- **web:** Portals copy + analytics semantics
- **web:** Ux-audit cleanup — CostBadge global CSS + last sub-44 stragglers

---

## career-ops v1.17.0 — July 5, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.17.0

### New

- Add the opt-in experimental web UI surface (web/) — isolated versioning, CI and packaging
- **add:** /career-ops add — fetch a project/paper/role into cv.md and article-digest.md
- **agent-inbox:** Queue requests for the next session
- **apply:** Pre-scan forms for knock-out questions before drafting
- **contacto:** Short-greeting variant for chat-style outreach
- **email:** Formal application email drafts — draft-only, never sends
- Experimental local-first web UI (opt-in alpha)
- **find:** Resolve a company/role/number query to its full pipeline identity
- **followup:** Seed a pinned first follow-up when a row turns Applied
- **i18n:** Add Korean career-ops modes
- **i18n:** Translate interview modes to French, closes #1489
- **i18n:** Translate interview modes to Spanish, closes #1490
- **oferta:** Flag 'remote' location contradicted by JD-body signals
- **patterns:** Recruiting-process friction signal at company level
- **patterns:** Transcript-driven targeting correction (Step 1b) — closes #960
- **pipeline:** Optional per-offer note in the pipeline writer
- **portals:** Probe non-ATS boards through the provider layer
- **profile:** Candidate contact-channel preference for outreach CTAs, closes #1556
- **providers:** Add Get on Board (getonbrd) zero-auth provider
- **providers:** Amazon (amazon.jobs) and Avature zero-token providers
- **providers:** Avature self-heals jobOffset→offset pagination
- **providers:** Beesite (milch & zucker) + softgarden providers
- **providers:** Dassault Systèmes zero-token provider (Exalead XML)
- **providers:** Honour api: on workday and smartrecruiters
- **providers:** SAP SuccessFactors (RMK) zero-token provider
- **providers:** SuccessFactors CSB strategy + bounded health-probe budget
- **registry:** Add startup boards plugin
- **reserve-report-num:** --count N contiguous range reservation
- **scan:** Report unreachable boards with error classification + doctor sweep
- **verify:** Detect duplicate and orphan reports
- **web:** Config microcopy humanized (P1.5)
- **web:** Cost affordance — CostBadge muted (P1.6)
- **web:** Dedupe bug reports at write — stable fingerprint + click-gated similar-issue search
- **web:** Empty-state free-scan button (P0.1)
- **web:** Extract cleanChips to a tested module + tab/CR paste delimiter
- **web:** Inbox triage — Abundance → Triage → Shortlist → Opt-in Score
- **web:** Mobile tap-targets ≥44px + FAB clearance
- **web:** Orange hierarchy — brand-soft Mark-applied + inbox cost legend (P1.4)
- **web:** Report progressive disclosure (P0.3+P1.8)
- **web:** Richer bug-report diagnostics — data-shape fingerprint, core version, API errors

### Fixed

- Correctness sweep across tracker, providers, and eval reporting, closes #1527
- **dedup:** Require exact company+role match so distinct same-company roles survive
- **infra:** Exempt web/ from the SYSTEM_PATHS coverage guard (isolation contract)
- **merge-tracker:** Detect TSV score/status column order by header, closes #1427
- **merge-tracker:** Guard tier-3 fuzzy dedup with req/job number, closes #1524
- **onboarding:** Auto-copy template seeds in doctor --json (#782, #1369)
- **paths:** Prefer cygpath over wslpath in toBashPath()
- **pdf:** Use CSS @page setup for margins
- **plugins:** Harden manifest path containment against symlink traversal
- Prevent notion plugin from mangling slash-formatted scores
- **providers:** Avature parser tolerates two live tenant markup variants
- **providers:** Harden workday pagination against 429s, API caps, and dateless tenants
- **providers:** Update Glints API to v2-alc/searchJobsV3, document Jobstreet GraphQL migration
- **providers:** Workday honors ctx.maxPages so the liveness probe stops cleanly
- **scan:** Load .env credentials via optional dotenv import
- **update:** Make updater timeout budgets configurable
- **update:** Preserve local CLAUDE.md additions during /career-ops update
- **updater:** Add .claude/settings.json to USER_PATHS, closes #1408
- **web:** Bump FOLLOW-UPS DUE tap-targets to 44px on mobile
- **web:** Pin turbopack.root to prevent Windows postcss OOM
- **web:** Point the 'Get one free' link at the free-AI-engine guide
- **web:** Restore the report-a-bug kit lost between the RC branch and main

---

## career-ops v1.16.0 — July 2, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.16.0

### New

- Add interview skill modes — plan, practice, debrief
- **apply:** ATS auto-fill for Greenhouse / Ashby / Lever — prepare, don't submit
- **discovery:** Seed company universe from VC portfolios (YC / a16z)…
- **patterns:** ATS channel yield analysis — algorithmic-monoculture aware
- **plugins:** Seed/successor model — community plugins can supersede a bundled reference
- **providers:** Add HigherEdJobs RSS provider
- **providers:** Add JibeApply provider
- **providers:** Add NoDesk provider
- **providers:** Add Teamtailor zero-auth RSS provider
- **registry:** Add career-ops-plugin-obsidian v0.1.0
- **registry:** Add career-ops-plugin-tavily v0.1.0
- **registry:** Add google-calendar plugin v0.1.0
- **scan:** Add seniority-tier classifier with optional skip_tiers fi…

### Fixed

- **cv-template:** Keep role titles from orphaning at page breaks
- **dashboard:** Map tracker columns by header name
- **followup-cadence:** Schedule first responded follow-up with responded_initial
- **liveness:** Add Ashby API rung so live jobs.ashbyhq.com postings aren't false-expired
- **pdf:** Anchor the output path-traversal guard to the repo root, not cwd
- **pdf:** Keep a right gutter so right-aligned CV content isn't clipped
- **pdf:** Thread manifest metadata through render options

---

## career-ops v1.15.0 — June 29, 2026

Release notes: https://github.com/career-ops-hq/career-ops/releases/tag/career-ops-v1.15.0

### New

- Add opt-in plugin system (engine + supply-chain governance + registry)
- Add optional profile photo support for resume template
- **dashboard:** Add cover letter hotkey to report viewer
- **dashboard:** Hotkeys to view (d) and regenerate (D) the CV PDF
- OpenRouter runner — free models, auto-blacklist, no Claude CLI required
- **providers:** Add 4 Day Week provider (public zero-auth API)
- **providers:** Add Arbeitnow provider
- **providers:** Add Hacker News "Who is hiring?" provider (Algolia API)
- **providers:** Add Himalayas provider
- **providers:** Add Jobicy provider
- **providers:** Add Jobspresso provider
- **providers:** Add Landing.jobs provider
- **providers:** Add Pinpoint ATS provider
- **providers:** Add Rippling ATS provider
- **providers:** Add The Hub provider
- **providers:** Add The Muse provider
- **scan:** Add detect-reposts.mjs repost detection from scan-history.tsv
- **scan:** Add JustJoin and NoFluffJobs providers
- **scan:** Persist compensation to pipeline.md
- **scripts:** Add build:dashboard and serve:dashboard npm scripts
- **updater:** Structural SYSTEM_PATHS coverage check

### Fixed

- **deps:** Update dependency playwright to v1.61.1
- **eval:** Bound research budget to a single-pass query cap
- **scan:** Tighter company matching and dedup order for cooldown filter
- **security:** Harden batch tmpfile, PDF output path, and tracker cells
- **tracker:** Share header-name column mapping across all readers
