Releases: nazboyko/applypack
Release list
v1.0.0 — first stable release
What's new
- The whole arc ships: find (22 sources, checked hourly) → verify (ghost-job checklist with evidence URLs) → tailor (deterministic resume scoring + the targeted editor) → apply (fact-gated cover letters, PDF/DOCX) → track (kanban + stale-application nudges).
- applypack.dev is live, with a browser demo of the real scoring module at /demo/ — edit the resume and watch the score recompute; adding a word the AI marked cannot-claim moves nothing.
- 5 swappable AI backends with auto-failover (Claude Code / Gemini / Codex CLIs, Anthropic API, any OpenAI-compatible endpoint incl. free local models); your data stays in your own Postgres.
Schema
- no schema changes since v0.11.1
Verification
- 741 tests green; production site + demo exercised in a real browser
References
v0.11.1 — rename + launch hygiene
What's new
- Renamed to ApplyPack (repo
nazboyko/applypack, old URLs redirect). Postgres role/db/volume keep theirjobhunternames — existing deployments need no migration; if you rename your local checkout folder, copy the Docker volume first. - SECURITY.md (private vulnerability reporting is enabled) + CODE_OF_CONDUCT.md; CONTRIBUTING states the MIT in-bound license.
- README: 22 sources (not 16), cover-letter row, fresh screenshots under the new brand (synthetic demo data).
User-Agentnow reports the real version, derived from package.json (guard-tested).
Schema
- no schema changes
Verification
- 739 tests green; dashboard rebuilt, all 9 routes 200, brand verified at 1200px and 375px, 0 console errors
References
v0.11.0 — apply-link flags
What's new
- Four apply-link flags join
Job.redFlagsat ingest:apply-url-missing,apply-url-unusable,apply-url-shortened,apply-url-not-an-application— visible on/jobs/:idand in Telegram alerts with no new UI. backfill-apply-link-flagsannotates already-stored rows without an AI call (additive, idempotent,--dry-runreads only).- The planned trust score (F13) was dropped after measuring its four penalties on all 814 stored jobs — three would have been wrong for our data (185 non-https rows are one host's stale Greenhouse config; every missing-URL row is a hand-pasted MANUAL job; the domain-mismatch check flags only synthetic aggregator rows).
Schema
- no schema changes
Verification
- 738 unit tests green; flags merged at all three persist paths; backfill dry-run measured against the full corpus
References
- ADR 0023 (apply-link flags, not a trust score) · plan §14 (F13) · PR #36
v0.10.0 — safe local defaults
What's new
- The dashboard no longer binds to every network by default. Running the project without Docker used to put an unauthenticated dashboard — jobs, resume text, cover letters, settings, the Telegram token form — on whatever network the machine was joined to. It now listens on loopback unless you say otherwise; Docker installs are unchanged, since compose already published the port on localhost only.
- A fresh install starts without an AI key. Copying
.env.exampleand running anything used to fail withANTHROPIC_API_KEY: required, including the dashboard where that key is configured. The dashboard now starts with no credential at all and the AI engine tab tells you which engines are usable. - The "Running without Docker" README section was verified step by step on a clean clone and now says what it actually takes:
DATABASE_URLis the only value you must set, and both commands run from the repository root. package.jsonandCHANGELOG.mdwere re-synced with the tag history (both had stopped at0.2.1while tags ran tov0.9.0).
Schema
- No schema changes.
Verification
lint:types+npm testgreen — 688 tests, +5 covering the config schema.- Clean-clone install re-run with the fixes: the dashboard boots from
.env.exampleverbatim with no API key, logshost: "127.0.0.1", answers 200 on loopback, and the LAN address is refused. 36 migrations applied to an empty database, 39 seed upserts, all dashboard routes and/static/*.mjs200. docker compose configconfirms thewebservice still resolvesWEB_HOST: 0.0.0.0.
References
- PR #34
- ADR 0013 / 0014 (engine chain resolves at runtime, so a boot-time credential check was redundant)
v0.9.0 — untrusted-content fences
What's new
- Job descriptions, resumes and pasted pages are wrapped in an explicit fence before any model sees them, with one shared directive stating the text inside is data, never instructions.
- The classifier — which every fetched job passes through and which had no protection at all — is covered, along with the resume, match, cover-letter, verification and paste-extraction prompts.
- An injection attempt is no longer silently ignored: it is recorded as a
prompt-injection-attemptred flag on the job, so the attempt itself becomes visible. - Verification, the only path with web search, additionally refuses to fetch a URL the posting nominates or to treat such a page as corroboration.
- A CI guard derives its roster from the code itself, so a prompt builder or an AI call site added later cannot skip the fence.
- Fixed a latent bug where posting text starting with a dash could be read as a command-line flag by the local Claude CLI, and tightened the liveness checker to re-verify a URL after redirects.
Schema
- No schema changes. The attempt is recorded through the
red_flagsarray that already existed.
Verification
- 683 tests green; the guard was shown to fail on a removed fence, an unregistered builder and a new AI call site;
bench:resume21/21 before and after; classifier scores stayed inside a measured noise band; one real cover letter generated with the fact gate passing.
References
v0.8.0 — cover letters + fact gate
What's new
- Cover letters. A "Cover letter" card on every job page drafts a short (120–180 word) letter from your resume, your confirmed facts and the posting — plus the resume match and researched company facts when they exist. Tone select, angle fields that are remembered between letters, in-place editing that autosaves, Regenerate, and "Save as PDF" / "Save as DOCX".
- A Cover letter page in the menu (
/letter): pick a job from a searchable list of your newest matching jobs, or bring a new posting by URL or pasted text. Writing a letter takes about 25 seconds — the fit score is not computed on this path, and the resume match and company research are opt-in. - Fact gate (F7). Shipped untagged with PR #31 and surfaced here: every generated letter is checked deterministically against your resume and confirmed facts before you see it. An invented number, employer, title or denied tool triggers one regeneration with the violations quoted; a second failure discards the letter, so nothing unverified is ever stored. Your own edits are flagged, never blocked.
- Per-engine Cover letter model on Settings → AI engine (an empty slot follows the resume model). All model pickers now save the moment you change them.
- Letters are written in plain English for a non-technical first reader and normalized to keyboard punctuation — no em dashes, curly quotes or bullets ever reach a stored letter.
Schema
CoverLettertable (20260831180000_add_cover_letter).AppSettings.coverAngles(20260831190000_add_cover_angles).
Verification
648 unit tests; 15+ real generations across five engine/model pairs, three input paths and three tones; live chain failover with the · fallback marker; SSRF guard on the URL fetch; 1200/375 screenshots, 0 console errors.
References
ADR 0020 (fact gate), ADR 0021 (letters generate from stored inputs only), docs/feature-expansion-plan.md §8–9, PRs #31 and #32.
v0.7.0 — source health monitoring
What's new
- Tracked boards now report whether they are actually answering. Every fetch records a per-source status:
ok,empty,slug not found,gated,rate-limited,vendor error,unreachable,unreadable payload. - A "Quiet sources" card at the top of
/companieslists boards that failed three ticks in a row (Failing) or stayed reachable but returned no posting for 14 days (Silent), each with one-click Re-probe to repair — a clean probe clears the streak. - A health dot on every company row, read from the board's raw output rather than from stored jobs, so a strict profile filter never looks like a broken board.
- Optional line in the daily Telegram digest naming sources that went quiet (
/settings→ Notifications → "Source health alerts", default on). - Turning this on immediately surfaced two boards in this deployment that had been failing silently:
GREENHOUSE:pleo(moved to Ashby) andLEVER:plaid.
Schema
20260831170000_add_source_health— additive:Company.lastFetchStatus,Company.consecutiveFailures,Company.lastOkAt,AppSettings.sourceHealthAlerts.
Verification
563 tests pass, tsc --noEmit clean; migration applied in-container; smoke ran a planted bad slug through three ticks (streak 1→2→3, threshold warning fired, row restored); every route 200, screenshots at 1200px and 375px, 0 console errors. npm run test:telegram skipped — escaping covered by unit tests.
References
- ADR 0019 — source health is a per-company streak;
emptyresets it but does not prove health - ADR 0016 — its deferred board-feed-vanish item was designed here and deferred again, with the gate written down
- Plan:
docs/feature-expansion-plan.md§5 (F4) — PR #30
v0.6.0 — cross-source dedup
What's new
- The same job arriving from two sources is now spotted by a content fingerprint (SimHash over the description) and flagged: "Also listed elsewhere — apply through one channel only" on the job page, in both directions, and as a line in the Telegram alert
- Nothing is merged or hidden — both rows stay and both are still classified, so a wrong flag costs a note and nothing else
backfill-fingerprints.jsfingerprints existing jobs and links what it finds; re-running it links nothing new- Feed rows that nothing identifies are skipped instead of sharing one synthesised id, and tracking parameters (
utm_*,gh_src,fbclid, …) no longer change a job's id — functional ones likegh_jidare kept
Measured, not assumed
The plan's constants were re-measured against the 731 stored jobs first, and two were wrong for this corpus:
- guard raised to 400 normalized characters — at 200, Jobicy's truncated teasers made two different roles at one company hash identically
- threshold raised to Hamming 7 — every cross-company match up to 7 was genuine, the first false positive lands at 10
- the plan's "skip same-company matches" was dropped: 27% of them are genuinely different roles sharing a company's boilerplate
Backfill on the existing corpus linked 6 real cross-listings, including Lemon.io across three aggregators and Reddit on both WeWorkRemotely and its own Greenhouse board.
Schema
Job.descriptionSimhash(BIGINT, nullable),Job.crossListedOfJobId(nullable self-FK, ON DELETE SET NULL), index on(fetchedAt, descriptionSimhash)— additive
Verification
- 524 unit tests green; migration applied through the container init path; backfill dry-run → real run → idempotent re-run; one live fetch tick; screenshots 1200/375, 0 console errors
References
v0.5.0 — company starter packs
What's new
/companies→ "Add a starter pack": curated segments of companies, each board re-probed live before anything is written- 86 companies across 5 segments (PHP/Laravel & CMS, JS infra & dev tools, JS/TS product & headless CMS, remote-first, UA-friendly remote)
- Preview before insert: new boards, boards already tracked, and unresolved names with a reason — a name is never dropped silently
- Companies land disabled, with an "Enable all" button after the import; re-importing a pack adds nothing
- ATS probe failures now say what actually happened — rate limiting and vendor outages no longer report "token likely invalid"
Schema
- no schema changes
Verification
- 500 unit tests green; live probe smoke on the PHP (23) and UA (11) segments; full UI round-trip in Docker incl. re-import and rejected picks; screenshots 1200/375, 0 console errors
References
v0.4.0 — fetchers wave 1
6 new sources: Recruitee, Breezy, BambooHR, Pinpoint, Rippling + 4 Day Week (robots-allowed /api/v2). JustJoin/NoFluffJobs/NoDesk rejected on robots grounds (ADR 0005 addendum). 6 enum migrations; 480 tests, full-tick smoke 848→27 classified, 0 zod errors.