Commit graph

367 commits

Author SHA1 Message Date
f62a9d9901 feat: llm_router shim — tri-level config priority (local > user > env-var) 2026-04-04 18:36:29 -07:00
b79d13b4f2 feat(vue): parity gaps #50, #54, #61 — sort/filter, research modal, draft CL button
Some checks failed
CI / test (push) Failing after 30s
#50 Job Review list view — sort + filter controls:
- Sort by best match / newest first / company A-Z (client-side computed)
- Remote-only checkbox filter
- Job count indicator; filters reset on tab switch
- Remote badge on list items

#61 Cover letter generation from approved tab:
- ' Draft' button on each approved-list item → /apply/:id
- No extra API call; ApplyWorkspace handles generation from there

#54 Company research modal (all API endpoints already existed):
- CompanyResearchModal.vue: 3-state machine (empty→generating→ready)
  polling /research/task every 3s, displays all 7 research sections
  (company, leadership, talking points, tech, funding, red flags,
  accessibility), copy-to-clipboard for talking points, ↺ Refresh
- InterviewCard: new 'research' emit + '🔍 Research' button for
  phone_screen/interviewing/offer stages
- InterviewsView: wires modal with researchJobId/Title/AutoGen state;
  auto-opens modal with autoGenerate=true when a job is moved to
  phone_screen (mirrors Streamlit behaviour)
2026-04-02 19:26:13 -07:00
5f4eecbc02 chore(release): v0.8.5
Some checks failed
CI / test (push) Failing after 27s
2026-04-02 18:47:33 -07:00
9069447cfc Merge pull request 'feat(wizard): Vue onboarding wizard + user config isolation fixes' (#65) from feature/vue-wizard into main
Some checks failed
CI / test (push) Failing after 26s
2026-04-02 18:46:42 -07:00
deeba0211d fix(isolation): 4 user config isolation + resume upload bugs
Some checks failed
CI / test (pull_request) Failing after 33s
- _user_yaml_path(): remove dangerous fallback to /devl/job-seeker/
  config/user.yaml (Meg's legacy profile); a missing user.yaml now
  returns an empty dict via load_user_profile, never another user's data
- RESUME_PATH: replace hardcoded relative Path('config/plain_text_
  resume.yaml') with _resume_path() that derives from _user_yaml_path()
  so resume file is always co-located with the correct user.yaml
- upload_resume: was passing a file path string to structure_resume()
  which expects raw text; now extracts bytes, dispatches to the correct
  extractor (pdf/odt/docx), then passes text — matches Streamlit wizard
- WizardResumeStep.vue: upload response is {ok, data: {experience…}}
  but component was reading data.experience (top level); fixed to
  read resp.data.experience to match the actual API envelope
2026-04-02 18:23:02 -07:00
e0828677a4 feat(wizard): Vue onboarding wizard — all 7 steps + router wiring
- WizardLayout.vue: full-page card, progress bar, crash-recovery via
  loadStatus(isCloud); auto-skips steps 1/2/5 in cloud mode
- wizard.css: shared step styles (headings, banners, radio cards,
  chip lists, form fields, expandables, nav buttons)
- Step 1 — Hardware: GPU detection, profile select, mismatch warning
- Step 2 — Tier: Free/Paid/Premium radio cards
- Step 3 — Resume: upload (PDF/DOCX/ODT) or manual experience builder;
  pre-fills identity fields from parsed resume data
- Step 4 — Identity: name/email/phone/LinkedIn/career summary;
  full validation before saveStep
- Step 5 — Inference: remote API keys vs local Ollama; advanced
  service-host/port expandable; soft-fail connection test
- Step 6 — Search: chip-style job title + location input with
  comma/Enter commit; remote-only checkbox
- Step 7 — Integrations: optional tile-grid (Notion/Calendar/Slack/
  Discord/Drive); paid-tier badge for gated items; calls
  wizard.complete() on Finish
- wizard.ts Pinia store: loadStatus, detectHardware, saveStep,
  testInference, complete; cloud auto-skip logic
- wizardGuard.ts: gates all routes behind /setup until
  wizard_complete; redirects complete users away from /setup
- router/index.ts: /setup nested route tree; unified beforeEach guard
  (wizard gate → setup branch → settings tier gate)
- App.vue: hide AppNav + strip sidebar margin on /setup routes
2026-04-02 18:11:57 -07:00
104c1e8581 feat(wizard): add Vue wizard API endpoints and wizardComplete/isDemo to app config
New endpoints:
- GET  /api/wizard/status       — resume-after-refresh; returns wizard_step + saved_data
- POST /api/wizard/step         — persist step data; side effects per step
                                  (step 3: plain_text_resume.yaml, step 5: .env keys,
                                   step 6: search_profiles.yaml)
- GET  /api/wizard/hardware     — GPU detection + profile suggestion
- POST /api/wizard/inference/test — soft-fail Ollama/LLM connectivity check
- POST /api/wizard/complete     — set wizard_complete=true, apply service URLs

Updated:
- GET /api/config/app now includes wizardComplete (from user.yaml) and isDemo
  (from DEMO_MODE env) so the Vue nav guard can gate on a single config fetch

30 tests, all passing
2026-04-02 17:55:03 -07:00
b6baa664b4 chore(release): v0.8.4
Some checks failed
CI / test (push) Failing after 30s
2026-04-02 17:47:26 -07:00
8b1d576e43 Merge pull request 'feat(vue): open Vue SPA to all tiers; fix cloud navigation and feedback button' (#64) from feature/vue-streamlit-parity into main
Some checks failed
CI / test (push) Failing after 28s
2026-04-02 17:46:47 -07:00
3313eade49 test: update cover letter mock signature to include user_yaml_path param
Some checks failed
CI / test (pull_request) Failing after 31s
2026-04-02 17:43:01 -07:00
b06d596d4c feat(vue): open Vue SPA to all tiers; fix cloud nav and feedback button
Some checks failed
CI / test (pull_request) Failing after 1m16s
- Lower vue_ui_beta gate to "free" so all licensed users can access the
  new UI without a paid subscription
- Remove "Paid tier" wording from the Try New UI banner
- Fix Vue SPA navigation in cloud/demo deployments: add VITE_BASE_PATH
  build arg so Vite sets the correct subpath base, and pass
  import.meta.env.BASE_URL to createWebHistory() so router links
  emit /peregrine/... paths that Caddy can match
- Fix feedback button missing on cloud instance by passing
  FORGEJO_API_TOKEN through compose.cloud.yml
- Remove vLLM container from compose.yml (vLLM dropped from stack;
  cf-research service in cfcore covers the use case)
- Fix cloud config path in Apply page (use get_config_dir() so per-user
  cloud data roots resolve correctly for user.yaml and resume YAML)
- Refactor generate_cover_letter._build_system_context and
  _build_mission_notes to accept explicit profile arg (enables
  per-user cover letter generation in cloud multi-tenant mode)
- Add API proxy block to nginx.conf (Vue web container can now call
  /api/ directly without Vite dev proxy)
- Update .env.example: remove vLLM vars, add research model + tuning
  vars for external vLLM deployments
- Update llm.yaml: switch vllm base_url to host.docker.internal
  (vLLM now runs outside Docker stack)

Closes #63 (feedback button)
Related: #8 (Vue SPA), #50–#62 (parity milestone)
2026-04-02 17:41:35 -07:00
66dc42a407 fix(preflight): remove vllm from Docker adoption list
vllm is now managed by cf-orch as a host process — no Docker service
defined in compose.yml. Preflight was detecting port 8000 (llm_server)
and generating a vllm stub in compose.override.yml with no image,
causing `docker compose up` to error on startup.
2026-04-02 16:57:06 -07:00
bc80922d61 chore(llm): swap model_candidates order — Qwen2.5-3B first, Phi-4-mini fallback
Phi-4-mini's cached modeling_phi3.py imports SlidingWindowCache which
was removed in transformers 5.x. Qwen2.5-3B uses built-in qwen2 arch
and works cleanly. Reorder so Qwen is tried first.
2026-04-02 16:36:38 -07:00
11fb3a07b4 chore(llm): switch vllm model_candidates from Ouro to Phi-4-mini + Qwen2.5-3B
Ouro models incompatible with transformers 5.x bundled in cf env.
Phi-4-mini-instruct tried first (stronger benchmarks, 7.2GB);
Qwen2.5-3B-Instruct as VRAM-constrained fallback (5.8GB).
2026-04-02 15:34:59 -07:00
7c9dcd2620 config(llm): add cf_orch block to vllm backend 2026-04-02 12:20:41 -07:00
13cd4c0d8a fix(cloud): mount llm.cloud.yaml over llm.yaml; restrict to vllm+ollama only
Some checks failed
CI / test (push) Failing after 17s
Remove claude_code, github_copilot, and anthropic from all cloud fallback
orders — cloud accounts must not route through personal/dev LLM backends.
vllm_research and ollama_research are the only permitted research backends.
llm.cloud.yaml is now bind-mounted at /app/config/llm.yaml in compose.cloud.yml.
2026-04-01 19:59:01 -07:00
5b296b3e01 fix(discovery): per-user config dir in cloud mode; normalize job_titles key
Some checks failed
CI / test (push) Failing after 22s
- discover.py: run_discovery() accepts config_dir param; auto-derives it
  from db_path parent (per-user in cloud, falls back to /app/config)
- task_runner.py: passes db_path.parent/config as config_dir to run_discovery
- wizard (0_Setup.py): write 'titles' key not 'job_titles' — matches what
  discover.py and all custom board scrapers read
- adzuna/theladders/craigslist: fall back to 'job_titles' for existing
  profiles written by older wizard versions
- Fixed Sheridan's live config in place (job_titles → titles)
2026-04-01 19:37:29 -07:00
4700a2f6d6 chore(release): v0.8.3
Some checks failed
CI / test (push) Failing after 22s
2026-04-01 15:02:19 -07:00
b3223025fa fix(api): read STAGING_DB from env at call time in _user_yaml_path()
Some checks failed
CI / test (push) Failing after 25s
Module-level DB_PATH is frozen at import time, so monkeypatch.setenv()
in tests had no effect on _user_yaml_path(). Reading os.environ directly
fixes 6 test_dev_api_settings PUT/POST failures in CI where
/devl/job-seeker/ doesn't exist.
2026-04-01 14:02:08 -07:00
5266aa52e8 ci: configure Forgejo git credentials before pip install
Some checks failed
CI / test (push) Failing after 27s
FORGEJO_TOKEN secret injected via env var (not inline expression) to
avoid CI injection risk; git insteadOf redirect authenticates the
git+https:// circuitforge-core VCS URL at install time.
2026-04-01 13:43:54 -07:00
d31a31b263 chore: release v0.8.2
Some checks failed
CI / test (push) Failing after 28s
2026-04-01 13:22:43 -07:00
a153546335 fix(ci): replace local -e path with Forgejo VCS URL for circuitforge-core
Some checks failed
CI / test (push) Has been cancelled
CI checks out only the peregrine repo — ../circuitforge-core doesn't exist,
causing 'pip install -r requirements.txt' to fail.

requirements.txt now uses git+https://...@main as the fallback for CI and
bare pip installs. Dockerfile.cfcore installs cfcore from the local COPY
first (skipping the requirements.txt line) to avoid a redundant network
fetch during Docker builds.
2026-04-01 13:22:06 -07:00
88c662f08d feat: switch main compose to Dockerfile.cfcore parent-context build
Some checks failed
CI / test (push) Failing after 31s
compose.yml was still using build: . which fails because requirements.txt
has -e ../circuitforge-core outside the build context. Now matches
compose.cloud.yml and compose.test-cfcore.yml.
2026-04-01 13:00:45 -07:00
83c87d4a13 feat(cloud): promote cfcore integration to production cloud instance
Some checks failed
CI / test (push) Failing after 19s
Switch compose.cloud.yml build context to Dockerfile.cfcore (parent
context includes circuitforge-core/ as sibling). Adds CF_ORCH_URL so
the cloud container can reach the cf-orch coordinator on the host.
2026-04-01 11:25:00 -07:00
d00d74d994 feat(scheduler): read CF_ORCH_URL env var for coordinator address
Some checks failed
CI / test (push) Failing after 19s
Threads coordinator_url from CF_ORCH_URL env var (default localhost:7700)
into the cfcore TaskScheduler so Docker instances can point at
host.docker.internal:7700 instead of the container's own loopback.

Also adds CF_ORCH_URL to compose.test-cfcore.yml and mounts persistent
patched configs (llm.docker.yaml, user.docker.yaml) for the test instance.
2026-04-01 11:06:38 -07:00
a8b08f3a45 fix: prevent Vue-nav reload loop when running without Caddy proxy
Some checks failed
CI / test (push) Failing after 20s
sync_ui_cookie() was calling window.parent.location.reload() on every
render when user.yaml has ui_preference=vue, but no Caddy is in the
traffic path (test instances, bare Docker). This caused an infinite
reload loop because the reload just came back to Streamlit.

Gate the reload on PEREGRINE_CADDY_PROXY=1. Without it, the cookie is
still written silently but no reload is attempted. Add the env var to
compose.yml and compose.cloud.yml (both are behind Caddy); omit from
compose.test-cfcore.yml so test instances stay stable.
2026-04-01 08:21:15 -07:00
be19947cb4 chore(release): v0.8.1
Some checks failed
CI / test (push) Failing after 42s
2026-04-01 07:25:13 -07:00
a6b32917ea chore(docker): add cfcore-aware Dockerfile and test compose
- Dockerfile: restored to original (build: . context, no cfcore) so
  existing compose.yml / compose.cloud.yml builds are unaffected
- Dockerfile.cfcore: parent-context build that copies circuitforge-core/
  alongside peregrine/ before pip install; resolves -e ../circuitforge-core
- compose.test-cfcore.yml: single-user test instance on port 8516;
  run from parent dir with context: .. so both repos are in scope

Use this to smoke-test cfcore shims before promoting to prod cloud.
2026-04-01 07:24:47 -07:00
2959abb3da fix(settings): improve suggest feedback for empty/failed LLM results
- Catch all exceptions (not just RuntimeError) so FileNotFoundError,
  connection errors, etc. surface as error messages rather than crashing
  the page silently
- Show "No new suggestions found" info message when the LLM returns
  empty arrays — previously the spinner completed with no UI feedback
- Hint to upload resume when RESUME_PATH is missing (new users)
- Only rerun() when there are actual results to display
2026-04-01 07:17:21 -07:00
98754cbe43 chore(release): v0.8.0 2026-04-01 07:12:48 -07:00
8c42de3f5c feat(merge): merge feature/vue-spa into main
Full Vue 3 SPA merge — closes #8. Major additions:

Backend (dev API):
- dev_api.py → symlink to dev-api.py (importable module alias)
- dev-api.py: full FastAPI backend (settings, jobs, interviews, prep,
  survey, digest, resume optimizer endpoints); cloud session middleware
- scripts/user_profile.py: load_user_profile / save_user_profile helpers
- scripts/discover.py + scripts/imap_sync.py: API-compatible additions

Frontend (web/src/):
- ApplyWorkspace: ATS resume optimizer panel (gap report free, rewrite paid+)
- ResumeOptimizerPanel.vue: new component with task polling + .txt download

Test suite:
- test_dev_api_settings/survey/prep/digest/interviews: full API test coverage
- fix: replace importlib.reload with monkeypatch.setattr(dev_api, "DB_PATH")
  to prevent module global reset breaking isolation across test files

Docs:
- docs/vue-spa-migration.md: migration guide
2026-04-01 07:11:14 -07:00
faa1807e96 feat(api): add job ranker and credential store scripts
- scripts/job_ranker.py: two-stage rank pipeline for /api/jobs/stack
  endpoint; scores pending jobs by match_score + seniority signals
- scripts/credential_store.py: per-user credential management (BYOK
  API keys, email passwords); used by dev_api settings endpoints
2026-04-01 07:10:46 -07:00
ee66b6b235 feat(web): add task indicator component and task store for background jobs
- web/src/stores/tasks.ts: Pinia store polling /api/tasks/active
- web/src/components/TaskIndicator.vue: sidebar + mobile task queue
  display with live count badge
- web/public/: peregrine logo assets (SVG + PNG variants)
2026-04-01 07:09:55 -07:00
02e004ee5c feat(apply): ATS resume optimizer backend — gap report + LLM rewrite
- scripts/resume_optimizer.py: full pipeline (extract_jd_signals →
  prioritize_gaps → rewrite_for_ats → hallucination_check)
- scripts/db.py: add optimized_resume + ats_gap_report columns +
  save_optimized_resume / get_optimized_resume helpers
- tests/test_resume_optimizer.py: 17 unit tests; patches at source
  module (scripts.llm_router.LLMRouter), not consumer

Tier gate: gap report is free; full LLM rewrite is paid+.
2026-04-01 07:09:46 -07:00
9702646738 fix(cloud): replace DEFAULT_DB with get_db_path() across all Streamlit pages
Pages were hardcoding DEFAULT_DB at import time, meaning cloud-mode
per-user DB routing was silently ignored. Pages affected:
1_Job_Review, 5_Interviews, 6_Interview_Prep, 7_Survey.

Adds resolve_session("peregrine") + get_db_path() pattern to each,
matching the pattern already used in 4_Apply.py.

Fixes #24.
2026-04-01 07:09:35 -07:00
dfac0f3d7a fix(tests): replace importlib.reload with monkeypatch.setattr for DB_PATH isolation
importlib.reload(dev_api) reset all module-level globals (RESUME_PATH,
SEARCH_PREFS_PATH, etc.) on every digest/interviews test, causing
subsequent monkeypatch.setattr calls in test_dev_api_settings.py to
silently fail — the patched attribute was reset between fixture setup
and the actual HTTP request.

Fix: patch dev_api.DB_PATH directly via monkeypatch, which pytest reverts
cleanly after each test without touching any other module state.

Also sync resume optimizer endpoints to dev-api.py (hyphen variant).
2026-04-01 06:58:28 -07:00
931a07d4e0 chore(merge): merge main into feature/vue-spa — resolve ApplyWorkspace conflict
ApplyWorkspace.vue: kept HEAD (vue-spa) version for resume optimizer panel,
cl-error__actions wrapper, and ResumeOptimizerPanel import. main's older
version lacked these additions.
2026-03-31 21:25:15 -07:00
faf0a7c4dc feat(apply): ATS resume optimizer — gap report + LLM rewrite (paid tier)
- scripts/resume_optimizer.py: extract_jd_signals, prioritize_gaps,
  rewrite_for_ats, hallucination_check, render_resume_text
- dev_api.py: GET/POST /api/jobs/{id}/resume_optimizer + /task endpoints
- web/src/components/ResumeOptimizerPanel.vue: gap report (all tiers),
  per-section LLM rewrite + hallucination badge (paid+)
- ApplyWorkspace.vue: ResumeOptimizerPanel wired in below cover letter

Closes #29
2026-03-31 21:24:49 -07:00
15dc4b2646 chore: rename conda env job-seeker to cf; update README
Some checks failed
CI / test (pull_request) Failing after 22s
2026-03-31 10:39:25 -07:00
922d91fb91 refactor(scheduler): shim to circuitforge_core.tasks.scheduler
VRAM detection now uses cf-orch free VRAM when coordinator is running,
making the scheduler cooperative with other cf-orch consumers.
Enqueue return value now checked — queue-full tasks are marked failed.
2026-03-31 09:27:43 -07:00
818e46c17e feat: migrate to circuitforge-core for db, llm router, and tiers
Some checks failed
CI / test (push) Failing after 24s
2026-03-25 11:44:19 -07:00
608e0fa922 fix(demo): block Vue navigation in demo mode; fix wizard gate ui sync
- ui_switcher.py: add explicit guard that forces pref=streamlit when
  DEMO_MODE=true, before the tier-downgrade check. Demo Vue SPA (#46)
  is not yet implemented, so navigating there produced a blank screen.
- app.py: call sync_ui_cookie inside wizard gate block before st.stop()
  so that cloud users with ui_preference=vue are redirected correctly
  even when the first-run wizard is still active. Previous behaviour
  called sync_ui_cookie after pg.run() which was never reached.
- demo/config/user.yaml: reset ui_preference to streamlit (belt-and-
  suspenders alongside the code guard).

Closes: demo blank-screen regression reported 2026-03-24.
2026-03-24 12:31:37 -07:00
e9c3c45612 fix(app): pass yaml_path and tier args to render_banner and sync_ui_cookie
Both functions require (yaml_path, tier) — calling them with no args was
silently failing inside the try/except, causing the banner to never render.
2026-03-22 19:28:25 -07:00
e95272c92f fix(app): show ui switcher banner in demo mode
render_banner() was incorrectly guarded by 'if not IS_DEMO' — the spec
says the banner is open to all demo visitors. render_banner() already
handles its own eligibility check internally (_DEMO_MODE or can_use).
2026-03-22 19:18:58 -07:00
c42ba318cf chore(release): v0.7.0
UI switcher (vue_ui_beta paid tier gate), demo toolbar (tier simulation),
Vue SPA merged from feature/vue-spa, nginx Docker web service, Caddy
cookie routing prgn_ui → :8506/:8507/:8508
2026-03-22 18:58:48 -07:00
8208731064 feat(docker): add web service for Vue SPA (nginx, multi-stage build)
Ports: dev=8506, demo=8507, cloud=8508
manage.sh build now includes web service
2026-03-22 18:47:46 -07:00
49e3265132 feat(web): merge Vue SPA from feature/vue-spa; add ClassicUIButton + useFeatureFlag
- Import web/ directory (Vue 3 + Vite + UnoCSS SPA) from feature/vue-spa branch
- Add web/src/components/ClassicUIButton.vue: switch-back to Streamlit via
  cookie (prgn_ui=streamlit) + ?prgn_switch=streamlit query param bridge
- Add web/src/composables/useFeatureFlag.ts: reads prgn_demo_tier cookie for
  demo toolbar visual consistency (not an authoritative gate, see issue #8)
- Update .gitignore: add .superpowers/, pytest-output.txt, docs/superpowers/
2026-03-22 18:46:11 -07:00
86de5d2f3f feat(settings): add ui_switcher toggle to Deployment expander 2026-03-22 16:50:17 -07:00
c94a9d5b30 chore(settings): remove old SettingsView placeholder — new shell at views/settings/SettingsView.vue
Full test suite: 71 frontend (14 files) + 583 backend tests passing.
2026-03-22 16:40:37 -07:00
17eed400f8 feat(app): wire ui_switcher and demo_toolbar into app.py render pass
- Initialize simulated_tier session state for demo mode after resolve_session/init_db
- Render demo toolbar before pg.run() when IS_DEMO is set
- Render ui_switcher banner before pg.run() for non-demo paid-tier users (guarded with try/except)
- Sync ui_preference cookie after pg.run() (guarded with try/except)
- All imports are local (inside if-blocks) to avoid Streamlit circular import issues
2026-03-22 16:30:52 -07:00