16 KiB
Changelog
All notable changes to Peregrine are documented here. Format follows Keep a Changelog.
[Unreleased]
[0.8.1] — 2026-04-01
Fixed
- Job title suggester silent failure — when the LLM returned empty arrays or non-JSON text, the spinner would complete with zero UI feedback. Now shows an explicit "No new suggestions found" info message with a resume-upload hint for new users who haven't uploaded a resume yet.
- Suggester exception handling — catch
Exceptioninstead of onlyRuntimeErrorso connection errors andFileNotFoundError(missing llm.yaml) surface as error messages rather than crashing the page silently.
Added
Dockerfile.cfcore— parent-context Dockerfile that copiescircuitforge-core/alongsideperegrine/beforepip install, resolving the-e ../circuitforge-coreeditable requirement inside Docker.compose.test-cfcore.yml— single-user test instance on port 8516 for smoke-testing cfcore shim integration before promoting to the cloud instance.
[0.8.0] — 2026-04-01
Added
-
ATS Resume Optimizer (gap report free; LLM rewrite paid+)
scripts/resume_optimizer.py— full pipeline: TF-IDF gap extraction →prioritize_gaps→rewrite_for_ats→ hallucination guard (anchor-set diffing on employers, institutions, and dates)scripts/db.py—optimized_resume+ats_gap_reportcolumns;save_optimized_resume/get_optimized_resumehelpersGET /api/jobs/{id}/resume_optimizer— fetch gap report + rewritePOST /api/jobs/{id}/resume_optimizer/generate— queue rewrite taskGET /api/jobs/{id}/resume_optimizer/task— poll task statusweb/src/components/ResumeOptimizerPanel.vue— gap report (all tiers), LLM rewrite section (paid+), hallucination warning badge,.txtdownloadResumeOptimizerPanelintegrated intoApplyWorkspace
-
Vue SPA full merge (closes #8) —
feature/vue-spamerged tomaindev-api.py— full FastAPI backend (settings, jobs, interviews, prep, survey, digest, resume optimizer); cloud session middleware (JWT → per-user SQLite); BYOK credential storedev_api.py— symlink →dev-api.pyfor importable module aliasscripts/job_ranker.py— two-stage ranking for/api/jobs/stackscripts/credential_store.py— per-user BYOK API key managementscripts/user_profile.py—load_user_profile/save_user_profileweb/src/components/TaskIndicator.vue+web/src/stores/tasks.ts— live background task queue displayweb/public/— peregrine logo assets (SVG + PNG)
-
API test suite — 5 new test modules (622 tests total)
tests/test_dev_api_settings.py(38 tests)tests/test_dev_api_interviews.py,test_dev_api_prep.py,test_dev_api_survey.py,test_dev_api_digest.py
Fixed
- Cloud DB routing —
app/pages/1_Job_Review.py,5_Interviews.py,6_Interview_Prep.py,7_Survey.pywere hardcodingDEFAULT_DB; now useget_db_path()for correct per-user routing in cloud mode (#24) - Test isolation —
importlib.reload(dev_api)in digest/interviews fixtures reset all module globals, silently breakingmonkeypatch.setattrin subsequent test files; replaced with targetedmonkeypatch.setattr(dev_api, "DB_PATH", tmp_db)(#26)
[0.7.0] — 2026-03-22
Added
-
Vue 3 SPA — beta access for paid tier — The new Vue 3 frontend (built with Vite + UnoCSS) is now merged into
mainand available to paid-tier subscribers as an opt-in beta. The Streamlit UI remains the default and will continue to receive full support.web/— full Vue 3 SPA source (components, stores, router, composables, views) fromfeature/vue-spaweb/src/components/ClassicUIButton.vue— one-click switch back to the Classic (Streamlit) UI; setsprgn_ui=streamlitcookie and appends?prgn_switch=streamlitsouser.yamlstays in syncweb/src/composables/useFeatureFlag.ts— readsprgn_demo_tiercookie for demo toolbar visual consistency (display-only, not an authoritative gate)
-
UI switcher — Reddit-style opt-in to the Vue SPA with durable preference persistence and graceful fallback.
app/components/ui_switcher.py—sync_ui_cookie(),switch_ui(),render_banner(),render_settings_toggle()scripts/user_profile.py—ui_preferencefield (streamlit|vue, default:streamlit) with round-tripsave()app/wizard/tiers.py—vue_ui_beta: "paid"feature key;demo_tierkeyword arg oncan_use()for thread-safe demo mode simulation- Banner (dismissible, paid tier only) + Settings → System → Deployment toggle
- Caddy cookie routing:
prgn_ui=vue→ nginx Vue SPA; absent/streamlit→ Streamlit. 502 fallback clears cookie and redirects with?ui_fallback=1
-
Demo toolbar — slim full-width tier-simulation bar for
DEMO_MODEinstances. Free / Paid / Premium pills let demo visitors explore all feature tiers without an account. Persists viaprgn_demo_tiercookie. Default: Paid (most compelling first impression).app/components/demo_toolbar.py -
Docker
webservice — multi-stage nginx container serving the Vue SPAdist/build. Added tocompose.yml(port 8506),compose.demo.yml(port 8507),compose.cloud.yml(port 8508).manage.sh buildnow includes thewebservice alongsideapp.
Changed
- Caddy routing —
menagerie.circuitforge.techanddemo.circuitforge.techperegrine blocks now inspect theprgn_uicookie and fan-out to the Vue SPA service or Streamlit accordingly.
[0.6.2] — 2026-03-18
Added
- Playwright E2E test harness — smoke + interaction test suite covering all
three Peregrine instances (demo / cloud / local). Navigates every page, checks
for DOM errors on load, clicks every interactable element, diffs errors
before/after each click, and XFAIL-marks expected demo-mode failures so
neutering-guard regressions are surfaced as XPASSes. Screenshots on failure.
tests/e2e/test_smoke.py— page-load error detectiontests/e2e/test_interactions.py— full click-through with XFAIL/XPASS bucketingtests/e2e/conftest.py— Streamlit-aware wait helpers, error scanner, fixturestests/e2e/models.py—ErrorRecord,ModeConfig,diff_errorstests/e2e/modes/— per-mode configs (demo / cloud / local)tests/e2e/pages/— page objects for all 7 pages including Settings tabs
Fixed
- Demo: "Discovery failed" error on Home page load —
task_runner.pynow checksDEMO_MODEbefore importingdiscover.py; returns a friendly error immediately instead of crashing on missingsearch_profiles.yaml(#21) - Demo: silent
st.error()in collapsed Practice Q&A expander — Interview Prep no longer auto-triggers the LLM on page render in demo mode; shows anst.infoplaceholder instead, eliminating the hidden error element (#22) - Cloud: auth wall shown to E2E test browser —
cloud_session.pynow falls back to theCookieheader whenX-CF-Sessionis absent (direct access without Caddy). Playwright'sset_extra_http_headers()does not propagate to WebSocket handshakes; cookies do. Test harness usesctx.add_cookies(). - E2E error scanner returned empty text for collapsed expanders — switched
from
inner_text()(respects CSSdisplay:none) totext_content()so errors inside collapsed Streamlit expanders are captured with their full text.
[0.6.1] — 2026-03-16
Fixed
- Keyword suggestions not visible on first render —
✨ Suggestin Settings → Search now callsst.rerun()after storing results; chips appear immediately without requiring a tab switch (#18) - Wizard identity step required manual re-entry of resume data — step 4 (Identity) now prefills name, email, and phone from the parsed resume when those fields are blank; existing saved values are not overwritten (#17)
- "Send to Notion" hardcoded on Home dashboard — sync section now shows the connected provider name, or a "Set up a sync integration" prompt with a Settings link when no integration is configured (#16)
test_generate_calls_llm_routerflaky in full suite — resolved by queue optimizer merge; mock state pollution eliminated (#12)
[0.6.0] — 2026-03-16
Added
- Calendar integration — push interview events to Apple Calendar (CalDAV) or
Google Calendar directly from the Interviews kanban. Idempotent: a second push
updates the existing event rather than creating a duplicate. Button shows
"📅 Add to Calendar" on first push and "🔄 Update Calendar" thereafter.
Event title:
{Stage}: {Job Title} @ {Company}; 1hr duration at noon UTC; job URL and company research brief included in event description.scripts/calendar_push.py— push/update orchestrationscripts/integrations/apple_calendar.py—create_event()/update_event()viacaldav+icalendarscripts/integrations/google_calendar.py—create_event()/update_event()viagoogle-api-python-client(service account);test()now makes a real API callscripts/db.py—calendar_event_id TEXTcolumn (auto-migration) +set_calendar_event_id()helperenvironment.yml— pincaldav>=1.3,icalendar>=5.0,google-api-python-client>=2.0,google-auth>=2.0
[0.4.1] — 2026-03-13
Added
- LinkedIn profile import — one-click import from a public LinkedIn profile URL
(Playwright headless Chrome, no login required) or from a LinkedIn data export zip.
Staged to
linkedin_stage.jsonso the profile is parsed once and reused across sessions without repeated network requests. Available on all tiers including Free.scripts/linkedin_utils.py— HTML parser with ordered CSS selector fallbacks; extracts name, experience, education, skills, certifications, summaryscripts/linkedin_scraper.py— Playwright URL scraper + export zip CSV parser; atomic staging file write; URL validation; robust error handlingscripts/linkedin_parser.py— staging file reader; re-runs HTML parser on stored raw HTML so selector improvements apply without re-scrapingapp/components/linkedin_import.py— shared Streamlit widget (status bar, preview, URL import, advanced zip upload) used by both wizard and Settings- Wizard step 3: new "🔗 LinkedIn" tab alongside Upload and Build Manually
- Settings → Resume Profile: collapsible "Import from LinkedIn" expander
- Dockerfile: Playwright Chromium install added to Docker image
Fixed
- Cloud mode perpetual onboarding loop — wizard gate in
app.pynow readsget_config_dir()/user.yaml(per-user in cloud, repo-level locally) instead of a hardcoded repo path; completing the wizard now correctly exits it in cloud mode - Cloud resume YAML path — wizard step 3 writes resume to per-user
CONFIG_DIRinstead of the shared repoconfig/(would have merged all cloud users' data) - Cloud session redirect — missing/invalid session token now JS-redirects to
circuitforge.tech/logininstead of showing a raw error message - Removed remaining AIHawk UI references (
Home.py,4_Apply.py,migrate.py)
[0.3.0] — 2026-03-06
Added
- Feedback button — in-app issue reporting with screenshot paste support; posts
directly to Forgejo as structured issues; available from sidebar on all pages
(
app/feedback.py,scripts/feedback_api.py,app/components/paste_image.py) - BYOK cloud backend detection —
scripts/byok_guard.py: pure Python detection engine with full unit test coverage (18 tests); classifies backends as cloud or local based on type,base_urlheuristic, and opt-outlocal: trueflag - BYOK activation warning — one-time acknowledgment required in Settings when a
new cloud LLM backend is enabled; shows data inventory (what leaves your machine,
what stays local), provider policy links; ack state persisted to
config/user.yamlunderbyok_acknowledged_backends - Sidebar cloud LLM indicator — amber badge on every page when any cloud backend is active; links to Settings; disappears when reverted to local-only config
- LLM suggest: search terms — three-angle analysis from resume (job titles, skills keywords, and exclude terms to filter irrelevant listings)
- LLM suggest: resume keywords — skills gap analysis against job descriptions
- LLM Suggest button in Settings → Search → Skills & Keywords section
- Backup/restore script (
scripts/backup.py) — multi-instance and legacy support PRIVACY.md— short-form privacy notice linked from Settings
Changed
- Settings save button for LLM Backends now gates on cloud acknowledgment before
writing
config/llm.yaml
Fixed
- Settings widget crash on certain rerun paths
- Docker service controls in Settings → System tab
DEFAULT_DBnow respectsSTAGING_DBenvironment variable (was silently ignoring it)generate()in cover letter refinement now correctly passesmax_tokenskwarg
Security / Privacy
- Full test suite anonymized — fictional "Alex Rivera" replaces all real personal data
in test fixtures (
tests/test_cover_letter.py,test_imap_sync.py,test_classifier_adapters.py,test_db.py) - Complete PII scrub from git history: real name, email address, and phone number
removed from all 161 commits across both branches via
git filter-repo
[0.2.0] — 2026-02-26
Added
- Cover letter iterative refinement: "Refine with Feedback" expander in Apply Workspace;
generate()acceptsprevious_result/feedback; task params passed throughsubmit_task - Expanded first-run wizard: 7-step onboarding with GPU detection, tier selection, resume upload/parsing, LLM inference test, search profile builder, integration cards
- Tier system: free / paid / premium feature gates (
app/wizard/tiers.py) - 13 integration drivers: Notion, Google Sheets, Airtable, Google Drive, Dropbox, OneDrive, MEGA, Nextcloud, Google Calendar, Apple Calendar, Slack, Discord, Home Assistant — with auto-discovery registry
- Resume parser: PDF (pdfplumber) and DOCX (python-docx) + LLM structuring
wizard_generatebackground task type with iterative refinement (feedback loop)- Dismissible setup banners on Home page (13 contextual prompts)
- Developer tab in Settings: tier override selectbox and wizard reset button
- Integrations tab in Settings: connect / test / disconnect all 12 non-Notion drivers
- HuggingFace token moved to Developer tab
paramscolumn inbackground_tasksfor wizard task payloadswizard_complete,wizard_step,tier,dev_tier_override,dismissed_banners,effective_tieradded to UserProfile- MkDocs documentation site (Material theme, 20 pages)
LICENSE-MITandLICENSE-BSL,CONTRIBUTING.md,CHANGELOG.md
Changed
app.pywizard gate now checkswizard_completeflag in addition to file existence- Settings tabs reorganised: Integrations tab added, Developer tab conditionally shown
- HF token removed from Services tab (now Developer-only)
Removed
- Dead
app/pages/3_Resume_Editor.py(functionality lives in Settings → Resume Profile)
[0.1.0] — 2026-02-01
Added
- Initial release: JobSpy discovery pipeline, SQLite staging, Streamlit UI
- Job Review, Apply Workspace, Interviews kanban, Interview Prep, Survey Assistant
- LLM router with fallback chain (Ollama, vLLM, Claude Code wrapper, Anthropic)
- Notion sync, email sync with IMAP classifier, company research with SearXNG
- Background task runner with daemon threads
- Vision service (moondream2) for survey screenshot analysis
- Adzuna, The Ladders, and Craigslist custom board scrapers
- Docker Compose profiles: remote, cpu, single-gpu, dual-gpu
setup.shcross-platform dependency installerscripts/preflight.pyandscripts/migrate.py