Commit graph

464 commits

Author SHA1 Message Date
8fda94356f Merge pull request 'feat: reranker pass in job ranking and ATS optimizer' (#109) from feature/reranker-integration into main
Some checks failed
CI / Backend (Python) (push) Failing after 1m13s
CI / Frontend (Vue) (push) Failing after 23s
Mirror / mirror (push) Failing after 7s
Release / release (push) Failing after 3s
2026-04-23 09:44:06 -07:00
2772b808d3 feat: reranker pass in job ranking and ATS optimizer (cf-core v0.15.0)
Some checks failed
CI / Backend (Python) (push) Failing after 1m10s
CI / Frontend (Vue) (push) Failing after 19s
CI / Backend (Python) (pull_request) Failing after 1m11s
CI / Frontend (Vue) (pull_request) Failing after 18s
- Add _try_rerank() to job_ranker.py: after stack_score sort, rerank
  top-50 candidates by (resume_text, description) cross-encoder relevance
- Add resume_text param to rank_jobs(); graceful no-op when empty
- Add reranker gap-reordering pass in rewrite_for_ats(): gaps sorted by
  (jd_text, term) relevance before section grouping and LLM rewrite
- Both integrations fall back silently on ImportError or inference failure
- 13 new tests; CF_RERANKER_MOCK=1 for zero-weight test runs
- Closes #108
2026-04-21 12:41:40 -07:00
ef8b857bf9 chore: remove Streamlit app service from compose.yml (#104)
Some checks failed
CI / Backend (Python) (push) Failing after 2m39s
CI / Frontend (Vue) (push) Failing after 20s
Mirror / mirror (push) Failing after 8s
Vue+FastAPI is now the only frontend. Streamlit is deprecated.
Container was stopped and removed.
2026-04-21 11:14:55 -07:00
4388a2d476 feat: add CF_APP_NAME=peregrine to dev compose for cf-orch pipeline attribution
Some checks failed
CI / Backend (Python) (push) Failing after 7m27s
CI / Frontend (Vue) (push) Failing after 20s
Mirror / mirror (push) Failing after 7s
2026-04-21 10:58:52 -07:00
f10c974fbb chore: release v0.9.0 — messaging tab, demo experience, references, resume sync
Some checks failed
CI / Backend (Python) (push) Failing after 1m35s
CI / Frontend (Vue) (push) Failing after 28s
Mirror / mirror (push) Failing after 15s
Release / release (push) Failing after 5s
2026-04-21 10:17:01 -07:00
5f92c52270 Merge pull request 'feat: public demo experience (Vue SPA with demo mode)' (#103) from feature/demo-experience into main
Some checks are pending
CI / Backend (Python) (push) Waiting to run
CI / Frontend (Vue) (push) Waiting to run
Mirror / mirror (push) Waiting to run
2026-04-21 10:15:02 -07:00
53c1b33b40 feat(demo): add UX designer resume, ATS optimizer snapshots, and company research briefs
Some checks failed
CI / Backend (Python) (push) Failing after 1m21s
CI / Frontend (Vue) (push) Failing after 18s
CI / Backend (Python) (pull_request) Failing after 1m10s
CI / Frontend (Vue) (pull_request) Failing after 22s
- Seed resumes table with a full UX designer base resume (Alex Rivera persona)
- Add ATS gap reports and optimized resumes for Spotify (job 1), Duolingo (job 2), NPR (job 3)
  - Each gap report highlights role-specific keyword opportunities (audio UX, gamification, public media)
  - Optimized resumes tailor the base resume framing to each company's emphasis
- Seed company_research for Asana (phone_screen), Notion (interviewing), Figma (hired)
  - Includes company_brief, ceo_brief, talking_points, tech_brief, funding_brief,
    competitors_brief, accessibility_brief for each
- Update demo/config/user.yaml career_summary to match UX designer persona
  - Fixes mismatch between "software engineer" summary and UX/design job seeds
  - Adds music and education mission preference notes
2026-04-21 10:14:37 -07:00
1c980cca51 docs: add screenshots and animated GIF to README
Six new screenshots (dashboard, card-stack review with approve/reject tint,
apply workspace, interviews kanban) plus an animated GIF of the swipe-review
flow. Adds demo link above the fold.
2026-04-21 10:14:37 -07:00
d02391d960 chore: update compose.demo.yml for Vue/FastAPI architecture
Replace the legacy Streamlit `app` service with a FastAPI `api` service
running dev_api:app on port 8601. The Vue SPA (nginx) proxies /api/ →
api:8601 internally so no host port is needed on the api container.

Move web service from port 8507 → 8504 to match the documented demo URL
(demo.circuitforge.tech/peregrine via Caddy → host:8504).
2026-04-21 10:14:37 -07:00
5bca5aaa20 fix: DemoBanner button contrast — use semantic surface token instead of hardcoded white
--color-primary in dark mode is a medium-light green (#6ab870); white on green
yields ~2.2:1 contrast (fails WCAG AA 4.5:1 minimum). Using --color-surface
(dark navy in dark mode, near-white in light mode) ensures the text always
contrasts strongly with the primary background regardless of theme.

Also tints banner background with 8% primary via color-mix() so it reads as
visually distinct from the page surface without being loud.
2026-04-21 10:14:37 -07:00
230cfb074c fix(demo): smoke-test fixes — card reset, toast error type, apply hint, text contrast
- JobCardStack: expose resetCard() to restore card after a blocked action
- JobReviewView: call resetCard() when approve/reject returns false; prevents
  card going blank after demo guard blocks the action
- useApi: add 'demo-blocked' to ApiError union; return truthy error from the
  403 interceptor so store callers bail early (no optimistic UI update)
- ApplyView: add HintChip to desktop split-pane layout (was mobile-only)
- HintChip: fix text color — --app-primary-light is near-white in light theme,
  causing invisible text; switch to --color-text for cross-theme contrast
- vite.config.ts: support VITE_API_TARGET env var for dev proxy override
- migrations/006: add date_posted, hired_feedback columns and references_ table
  (columns existed in live DB but were missing from migration history)
- DemoBanner: commit component and test (were untracked)
2026-04-21 10:14:37 -07:00
302033598c feat(demo): switch demo data volume to tmpfs, wire DEMO_SEED_FILE 2026-04-21 10:14:37 -07:00
ad26f02d5f feat(demo): add committed seed SQL and startup loader 2026-04-21 10:14:36 -07:00
03206aa34c feat(demo): add IS_DEMO write-block guard on mutating endpoints 2026-04-21 10:14:15 -07:00
55f464080f feat(demo): wire DemoBanner, WelcomeModal, HintChip into app + views 2026-04-21 10:14:15 -07:00
d96cdfa89b feat(demo): add HintChip component with per-view localStorage dismiss 2026-04-21 10:14:15 -07:00
a16d562e06 feat(demo): add WelcomeModal with localStorage gate 2026-04-21 10:14:15 -07:00
63334f5278 feat: messaging tab — messages, templates, draft reply (#74)
Some checks failed
CI / Backend (Python) (push) Failing after 1m48s
CI / Frontend (Vue) (push) Failing after 21s
Mirror / mirror (push) Failing after 7s
Merges feature/messaging-tab into main.

Features:
- Migration 008: messages + message_templates tables with 4 built-in templates
- API endpoints: CRUD for messages and templates, draft-reply (BYOK tier gate), approve
- PUT /api/messages/{id} for draft body persistence
- Pinia store (messaging.ts) with full action set
- MessageLogModal: log calls and in-person meetings with backdated timestamps
- MessageTemplateModal: apply (with token substitution + highlight), create, edit
- MessagingView: two-panel job list + UNION timeline (contacts + messages), Osprey easter egg
- Router: /messages route, /contacts redirect, nav renamed Messages
- Integration test suite (8 tests, 766 total passing)
- CRITICAL fix: _get_effective_tier() no longer trusts X-CF-Tier client header
2026-04-20 20:26:41 -07:00
b1e92b0e52 feat(docker): add /peregrine/ base-path routing in nginx
Adds location blocks for /peregrine/assets/ and /peregrine/ so the SPA
works correctly when accessed via a Caddy prefix that does not strip the
path (e.g. direct host access without reverse proxy stripping).
2026-04-20 20:26:31 -07:00
91e2faf5d0 fix: tier bypass, draft body persistence, canDraftLlm cleanup, limit cap
- CRITICAL: Remove X-CF-Tier header trust from _get_effective_tier; use
  Heimdall in cloud mode and APP_TIER env var in single-tenant only
- HIGH: Add update_message_body helper + PUT /api/messages/{id} endpoint;
  updateMessageBody store action; approveDraft now persists edits to DB
  before calling approve so history always shows the final approved text
- Cleanup: Remove dead canDraftLlm ref, checkLlmAvailable function, and
  v-else-if Enable LLM drafts link; show Draft reply button unconditionally
- MEDIUM: Cap GET /api/messages limit param with Query(ge=1, le=1000)
- Test: Update test_draft_without_llm_returns_402 to patch effective_tier
  instead of sending X-CF-Tier header
2026-04-20 17:19:17 -07:00
6812e3f9ef feat: /messages route + /contacts redirect + nav rename (#74) 2026-04-20 13:04:27 -07:00
899cd3604b feat: MessagingView two-panel layout + draft approval + Osprey easter egg (#74) 2026-04-20 13:02:24 -07:00
aa09b20e7e feat: MessageTemplateModal component (apply/create/edit modes) (#74) 2026-04-20 12:58:00 -07:00
b77ec81cc6 fix: thread logged_at through message stack; Esc handler and localNow fixes
- scripts/messaging.py: add logged_at param to create_message; use provided value or fall back to _now_utc()
- dev-api.py: add logged_at: Optional[str] = None to MessageCreateBody
- web/src/stores/messaging.ts: remove logged_at from Omit, add as optional intersection so callers can pass it through
- web/src/components/MessageLogModal.vue: pass logged_at in handleSubmit payload; move @keydown.esc from backdrop to modal-dialog (which holds focus); compute localNow fresh inside watch so it reflects actual open time
2026-04-20 12:55:41 -07:00
8df3297ab6 feat: MessageLogModal component (#74) 2026-04-20 12:52:19 -07:00
222eb4a088 fix: messaging store error handling and Content-Type headers 2026-04-20 12:50:51 -07:00
47a40c9e36 feat: messaging Pinia store (#74) 2026-04-20 12:48:15 -07:00
dfcc264aba test: use db.add_contact helper in integration test fixture
Replace raw sqlite3 INSERT in test_draft_without_llm_returns_402 with
add_contact() so the fixture stays in sync with schema changes
automatically.
2026-04-20 12:45:47 -07:00
d3dfd015bf feat(cloud): add CF_APP_NAME=peregrine for coordinator pipeline attribution
Allocations from peregrine cloud containers were showing pipeline=null
in cf-orch analytics. Adding CF_APP_NAME to both app and api service
blocks so LLMRouter passes it as the pipeline tag on each allocation.
2026-04-20 12:43:05 -07:00
e11750e0e6 test: messaging HTTP integration tests (#74) 2026-04-20 12:41:45 -07:00
715a8aa33e feat: LLM reply draft, tiers BYOK gate, and messaging API endpoints (#74) 2026-04-20 12:36:16 -07:00
091834f1ae test: add missing update_template KeyError test (#74) 2026-04-20 12:32:35 -07:00
ea961d6da9 feat: messaging DB helpers + unit tests (#74) 2026-04-20 11:55:43 -07:00
9eca0c21ab feat: migration 008 — messages + message_templates tables (#74) 2026-04-20 11:51:59 -07:00
5020144f8d fix: update interview + survey tests for hired_feedback column and async analyze endpoint 2026-04-20 11:48:22 -07:00
9101e716ba fix: async survey/analyze via task queue (#107)
Move POST /api/jobs/:id/survey/analyze off the FastAPI worker thread
by routing it through the LLM task queue (same pattern as cover_letter,
company_research, resume_optimize).

- Extract prompt builders + run_survey_analyze() to scripts/survey_assistant.py
- Add survey_analyze to LLM_TASK_TYPES (task_scheduler.py) with 2.5 GB VRAM budget
  (text mode: phi3:mini; visual mode uses vision service's own VRAM pool)
- Add elif branch in task_runner._run_task; result stored as JSON in error col
- Replace sync endpoint body with submit_task(); add GET /survey/analyze/task poll
- Update survey.ts store: analyze() now fires task + polls at 3s interval;
  silently attaches to existing in-flight task when is_new=false
- SurveyView button label shows task stage while polling

Fixes load-test spike: ~22 greenlets blocking on LLM inference at 100 concurrent
users, causing 90s poll timeouts on cover_letter and research tasks.
2026-04-20 11:06:14 -07:00
acc04b04eb docs(config): add cf_text and cf_voice trunk service backends to llm.yaml.example
Documents the cf-orch allocation pattern for cf-text and cf-voice as
openai_compat backends with a cf_orch block. Products enable these when
CF_ORCH_URL is set; the router allocates via the broker and calls the
managed service directly. No catalog or leaf details here — those live
in cf-orch node profiles (The Orchard trunk/leaf split).
2026-04-20 10:56:22 -07:00
280f4271a5 feat: add Plausible analytics to Vue SPA and docs
Some checks failed
CI / Backend (Python) (push) Failing after 1m13s
CI / Frontend (Vue) (push) Failing after 20s
Mirror / mirror (push) Failing after 7s
2026-04-16 21:15:55 -07:00
1c9bfc9fb6 test: integration tests for resume library<->profile sync endpoints 2026-04-16 14:29:00 -07:00
22bc57242e feat: ResumeProfileView — career_summary, education, achievements sections and sync status label 2026-04-16 14:22:36 -07:00
9f984c22cb feat: resume store — add career_summary, education, achievements, lastSynced state
Extends the resume Pinia store with EducationEntry interface, four new
refs (career_summary, education, achievements, lastSynced), education
CRUD helpers, and load/save wiring for all new fields. lastSynced is
set to current ISO timestamp on successful save.
2026-04-16 14:15:07 -07:00
fe3e4ff539 feat: ResumesView — Apply to profile button, Active profile badge, sync notice, unsaved-changes guard 2026-04-16 14:13:44 -07:00
43599834d5 feat: ResumeSyncConfirmModal — before/after confirmation for profile sync 2026-04-16 14:11:37 -07:00
fe5371613e feat: extend PUT /api/settings/resume to sync content back to default library entry
When a default_resume_id is set in user.yaml, saving the resume profile
now calls profile_to_library and update_resume_content to keep the
library entry in sync. Returns {"ok": true, "synced_library_entry_id": <int|null>}.
2026-04-16 14:09:56 -07:00
369bf68399 feat: POST /api/resumes/{id}/apply-to-profile — library→profile sync with auto-backup 2026-04-16 14:06:52 -07:00
eef6c33d94 feat: add EducationEntry model, extend ResumePayload with education/achievements/career_summary
- Add EducationEntry Pydantic model (institution, degree, field, start_date, end_date)
- Extend ResumePayload with career_summary str, education List[EducationEntry], achievements List[str]
- Rewrite _normalize_experience to pass through Vue-native format (period/responsibilities keys) unchanged; AIHawk format (key_responsibilities/employment_period) still converted
- Extend GET /api/settings/resume to fall back to user.yaml for legacy career_summary when resume YAML is missing or the field is empty
2026-04-16 14:02:59 -07:00
53bfe6b326 feat: add update_resume_synced_at and update_resume_content db helpers
Expose synced_at in _resume_as_dict (with safe fallback for pre-migration
DBs), and add two new helpers: update_resume_synced_at (library→profile
direction) and update_resume_content (profile→library direction, updates
text/struct_json/word_count/synced_at/updated_at).
2026-04-16 13:14:10 -07:00
cd787a2509 fix: period split in profile_to_library handles ISO dates with hyphens
Fixes a bug where ISO-formatted dates (e.g. '2023-01 – 2025-03') in the
period field were split incorrectly. The old code replaced the en-dash with
a hyphen first, then split on the first hyphen, causing dates like '2023-01'
to be split into '2023' and '01' instead of the expected start/end pair.

The fix splits on the dash/dash separator *before* normalizing to plain
hyphens, ensuring round-trip conversion of dates with embedded hyphens.

Adds two regression tests:
- test_profile_to_library_period_split_iso_dates: verifies en-dash separation
- test_profile_to_library_period_split_em_dash: verifies em-dash separation
2026-04-16 13:11:22 -07:00
048a5f4cc3 feat: resume_sync.py — library↔profile transform functions with tests
Pure transform functions (no LLM, no DB) bridging the two resume
representations: library struct_json ↔ ResumePayload content fields.
Exports library_to_profile_content, profile_to_library,
make_auto_backup_name, blank_fields_on_import. 22 tests, all passing.
2026-04-16 13:04:56 -07:00
fe4947a72f feat: add synced_at column to resumes table (migration 007) 2026-04-16 12:58:00 -07:00