peregrine/CHANGELOG.md
pyr0ball 5f4eecbc02
Some checks failed
CI / test (push) Failing after 27s
chore(release): v0.8.5
2026-04-02 18:47:33 -07:00

22 KiB

Changelog

All notable changes to Peregrine are documented here. Format follows Keep a Changelog.


[Unreleased]


[0.8.5] — 2026-04-02

Added

  • Vue onboarding wizard — 7-step first-run setup replaces the Streamlit wizard in the Vue SPA: Hardware detection → Tier → Resume upload/build → Identity → Inference & API keys → Search preferences → Integrations. Progress saves to user.yaml on every step; crash-recovery resumes from the last completed step.
  • Wizard API endpointsGET /api/wizard/status, POST /api/wizard/step, GET /api/wizard/hardware, POST /api/wizard/inference/test, POST /api/wizard/complete. Inference test always soft-fails so Ollama being unreachable never blocks setup completion.
  • Cloud auto-skip — cloud instances automatically complete steps 1 (hardware), 2 (tier), and 5 (inference) and drop the user directly on the Resume step.
  • wizardGuard router gate — all Vue routes require wizard completion; completed users are bounced away from /setup to /.
  • Chip-input search step — job titles and locations entered as press-Enter/comma chips; validates at least one title before advancing.
  • Integrations tile grid — optional step 7 shows Notion, Calendar, Slack, Discord, Drive with paid-tier badges; skippable on Finish.

Fixed

  • User config isolation: dangerous fallback removed_user_yaml_path() fell back to /devl/job-seeker/config/user.yaml (legacy profile) when user.yaml didn't exist at the expected path; new users now get an empty dict instead of another user's data. Affects profile, resume, search, and all wizard endpoints.
  • Resume path not user-isolatedRESUME_PATH = Path("config/plain_text_resume.yaml") was a relative CWD path shared across all users. Replaced with _resume_path() derived from _user_yaml_path() / STAGING_DB.
  • Resume upload silently returned empty dataupload_resume was passing a file path string to structure_resume() which expects raw text; now reads bytes and dispatches to the correct extractor (extract_text_from_pdf / _docx / _odt).
  • Wizard resume step read wrong envelope fieldWizardResumeStep.vue read data.experience but the upload response wraps parsed data under data.data.

[0.8.4] — 2026-04-02

Fixed

  • Cloud: cover letter used wrong user's profilegenerate_cover_letter.generate() loaded _profile from the global config/user.yaml at module import time, so all cloud users got the default user's name, voice, and mission preferences in their generated letters. generate() now accepts a user_yaml_path parameter; task_runner derives it from the per-user config directory (db_path/../config/user.yaml) and passes it through. _build_system_context, _build_mission_notes, detect_mission_alignment, build_prompt, and _trim_to_letter_end all accept a profile override so the per-call profile is used end-to-end without breaking CLI mode.
  • Apply Workspace: hardcoded config paths in cloud mode4_Apply.py was loading _USER_YAML and RESUME_YAML from the repo-root config/ before resolve_session() ran, so cloud users saw the global (Meg's) resume in the Apply tab. Both paths now derive from get_config_dir() after session resolution.

Changed

  • Vue SPA open to all tiers — Vue 3 frontend is no longer gated behind the beta flag; all tier users can switch to the Vue UI from Settings.
  • LLM model candidates — vllm backend now tries Qwen2.5-3B first, Phi-4-mini as fallback (was reversed). cf_orch allocation block added to vllm config.
  • Preflight — removed vllm from Docker adoption list; vllm is now managed entirely by cf-orch and should not be stubbed by preflight.

[0.8.3] — 2026-04-01

Fixed

  • CI: Forgejo auth — GitHub Actions pip install was failing to fetch circuitforge-core from the private Forgejo VCS URL. Added FORGEJO_TOKEN repository secret and a git config insteadOf step to inject credentials before pip install.
  • CI: settings API tests — 6 test_dev_api_settings PUT/POST tests were returning HTTP 500 in CI because _user_yaml_path() read the module-level DB_PATH constant (frozen at import time), so monkeypatch.setenv("STAGING_DB") had no effect. Fixed by reading os.environ at call time.

[0.8.2] — 2026-04-01

Fixed

  • CI pipelinepip install -r requirements.txt was failing in GitHub Actions because -e ../circuitforge-core requires a sibling directory that doesn't exist in a single-repo checkout. Replaced with a git+https:// VCS URL fallback; Dockerfile.cfcore still installs from the local COPY to avoid redundant network fetches during Docker builds.
  • Vue-nav reload loopsync_ui_cookie() was calling window.parent.location.reload() on every render when user.yaml has ui_preference: vue but no Caddy proxy is in the traffic path (test instances, bare Docker). Gated the reload on PEREGRINE_CADDY_PROXY=1; instances without the env var set the cookie silently and skip the reload.

Changed

  • cfcore VRAM lease integration — the task scheduler now acquires a VRAM lease from the cf-orch coordinator before running a batch of LLM tasks and releases it when the batch completes. Visible in the coordinator dashboard at :7700.
  • CF_ORCH_URL env var — scheduler reads coordinator address from CF_ORCH_URL (default http://localhost:7700); set to http://host.docker.internal:7700 in Docker compose files so containers can reach the host coordinator.
  • All compose files on Dockerfile.cfcorecompose.yml, compose.cloud.yml, and compose.test-cfcore.yml all use the parent-context build. build: . is removed from compose.yml.

[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 Exception instead of only RuntimeError so connection errors and FileNotFoundError (missing llm.yaml) surface as error messages rather than crashing the page silently.

Added

  • Dockerfile.cfcore — parent-context Dockerfile that copies circuitforge-core/ alongside peregrine/ before pip install, resolving the -e ../circuitforge-core editable 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_gapsrewrite_for_ats → hallucination guard (anchor-set diffing on employers, institutions, and dates)
    • scripts/db.pyoptimized_resume + ats_gap_report columns; save_optimized_resume / get_optimized_resume helpers
    • GET /api/jobs/{id}/resume_optimizer — fetch gap report + rewrite
    • POST /api/jobs/{id}/resume_optimizer/generate — queue rewrite task
    • GET /api/jobs/{id}/resume_optimizer/task — poll task status
    • web/src/components/ResumeOptimizerPanel.vue — gap report (all tiers), LLM rewrite section (paid+), hallucination warning badge, .txt download
    • ResumeOptimizerPanel integrated into ApplyWorkspace
  • Vue SPA full merge (closes #8) — feature/vue-spa merged to main

    • dev-api.py — full FastAPI backend (settings, jobs, interviews, prep, survey, digest, resume optimizer); cloud session middleware (JWT → per-user SQLite); BYOK credential store
    • dev_api.py — symlink → dev-api.py for importable module alias
    • scripts/job_ranker.py — two-stage ranking for /api/jobs/stack
    • scripts/credential_store.py — per-user BYOK API key management
    • scripts/user_profile.pyload_user_profile / save_user_profile
    • web/src/components/TaskIndicator.vue + web/src/stores/tasks.ts — live background task queue display
    • web/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 routingapp/pages/1_Job_Review.py, 5_Interviews.py, 6_Interview_Prep.py, 7_Survey.py were hardcoding DEFAULT_DB; now use get_db_path() for correct per-user routing in cloud mode (#24)
  • Test isolationimportlib.reload(dev_api) in digest/interviews fixtures reset all module globals, silently breaking monkeypatch.setattr in subsequent test files; replaced with targeted monkeypatch.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 main and 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) from feature/vue-spa
    • web/src/components/ClassicUIButton.vue — one-click switch back to the Classic (Streamlit) UI; sets prgn_ui=streamlit cookie and appends ?prgn_switch=streamlit so user.yaml stays in sync
    • web/src/composables/useFeatureFlag.ts — reads prgn_demo_tier cookie 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.pysync_ui_cookie(), switch_ui(), render_banner(), render_settings_toggle()
    • scripts/user_profile.pyui_preference field (streamlit | vue, default: streamlit) with round-trip save()
    • app/wizard/tiers.pyvue_ui_beta: "paid" feature key; demo_tier keyword arg on can_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_MODE instances. Free / Paid / Premium pills let demo visitors explore all feature tiers without an account. Persists via prgn_demo_tier cookie. Default: Paid (most compelling first impression). app/components/demo_toolbar.py

  • Docker web service — multi-stage nginx container serving the Vue SPA dist/ build. Added to compose.yml (port 8506), compose.demo.yml (port 8507), compose.cloud.yml (port 8508). manage.sh build now includes the web service alongside app.

Changed

  • Caddy routingmenagerie.circuitforge.tech and demo.circuitforge.tech peregrine blocks now inspect the prgn_ui cookie 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 detection
    • tests/e2e/test_interactions.py — full click-through with XFAIL/XPASS bucketing
    • tests/e2e/conftest.py — Streamlit-aware wait helpers, error scanner, fixtures
    • tests/e2e/models.pyErrorRecord, ModeConfig, diff_errors
    • tests/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 loadtask_runner.py now checks DEMO_MODE before importing discover.py; returns a friendly error immediately instead of crashing on missing search_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 an st.info placeholder instead, eliminating the hidden error element (#22)
  • Cloud: auth wall shown to E2E test browsercloud_session.py now falls back to the Cookie header when X-CF-Session is absent (direct access without Caddy). Playwright's set_extra_http_headers() does not propagate to WebSocket handshakes; cookies do. Test harness uses ctx.add_cookies().
  • E2E error scanner returned empty text for collapsed expanders — switched from inner_text() (respects CSS display:none) to text_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✨ Suggest in Settings → Search now calls st.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_router flaky 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 orchestration
    • scripts/integrations/apple_calendar.pycreate_event() / update_event() via caldav + icalendar
    • scripts/integrations/google_calendar.pycreate_event() / update_event() via google-api-python-client (service account); test() now makes a real API call
    • scripts/db.pycalendar_event_id TEXT column (auto-migration) + set_calendar_event_id() helper
    • environment.yml — pin caldav>=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.json so 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, summary
    • scripts/linkedin_scraper.py — Playwright URL scraper + export zip CSV parser; atomic staging file write; URL validation; robust error handling
    • scripts/linkedin_parser.py — staging file reader; re-runs HTML parser on stored raw HTML so selector improvements apply without re-scraping
    • app/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.py now reads get_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_DIR instead of the shared repo config/ (would have merged all cloud users' data)
  • Cloud session redirect — missing/invalid session token now JS-redirects to circuitforge.tech/login instead 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 detectionscripts/byok_guard.py: pure Python detection engine with full unit test coverage (18 tests); classifies backends as cloud or local based on type, base_url heuristic, and opt-out local: true flag
  • 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.yaml under byok_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_DB now respects STAGING_DB environment variable (was silently ignoring it)
  • generate() in cover letter refinement now correctly passes max_tokens kwarg

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() accepts previous_result/feedback; task params passed through submit_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_generate background 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
  • params column in background_tasks for wizard task payloads
  • wizard_complete, wizard_step, tier, dev_tier_override, dismissed_banners, effective_tier added to UserProfile
  • MkDocs documentation site (Material theme, 20 pages)
  • LICENSE-MIT and LICENSE-BSL, CONTRIBUTING.md, CHANGELOG.md

Changed

  • app.py wizard gate now checks wizard_complete flag 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.sh cross-platform dependency installer
  • scripts/preflight.py and scripts/migrate.py