Commit graph

232 commits

Author SHA1 Message Date
a0bdf7677f Merge pull request 'feat: push interview events to connected calendar integrations' (#20) from feature/calendar-push into main
All checks were successful
CI / test (push) Successful in 45s
Reviewed-on: #20
2026-03-16 21:45:06 -07:00
c1ec1fc9f6 feat: push interview events to connected calendar integrations (#19)
All checks were successful
CI / test (pull_request) Successful in 53s
Implements idempotent calendar push for Apple Calendar (CalDAV) and
Google Calendar from the Interviews kanban.

- db: add calendar_event_id column (migration) + set_calendar_event_id helper
- integrations/apple_calendar: create_event / update_event via caldav + icalendar
- integrations/google_calendar: create_event / update_event via google-api-python-client;
  test() now makes a real API call instead of checking file existence
- scripts/calendar_push: orchestrates push/update, builds event title from stage +
  job title + company, attaches job URL and company brief to description,
  defaults to noon UTC / 1hr duration
- app/pages/5_Interviews: "Add to Calendar" / "Update Calendar" button shown
  when interview date is set and a calendar integration is configured
- environment.yml: pin caldav, icalendar, google-api-python-client, google-auth
- tests/test_calendar_push: 9 tests covering create, update, error handling,
  event timing, idempotency, and missing job/date guards
2026-03-16 21:31:22 -07:00
2fcab541c7 fix: bootstrap resume_keywords.yaml on first cloud session
New cloud users got a "resume_keywords.yaml not found" warning in
Settings → Skills & Keywords because the file was never created during
account provisioning. resolve_session() now writes an empty scaffold
(skills/domains/keywords: []) to the user's config dir on first visit
if the file doesn't exist, consistent with how config/ and data/ dirs
are already created. Never overwrites an existing file.
2026-03-16 12:01:25 -07:00
84ae348f16 fix: auto-provision free license on first cloud session, fix score button in Docker
- cloud_session.py: add _ensure_provisioned() called in resolve_session() so
  new Google OAuth signups get a free Heimdall key created on first page load;
  previously resolve returned "free" tier but no key was ever written to
  Heimdall, leaving users in an untracked state
- Home.py: replace conda run invocation in "Score All Unscored Jobs" with
  sys.executable so the button works inside Docker where conda is not present
2026-03-16 11:51:15 -07:00
b4116e8bae feat: add pre-commit sensitive file blocker and support request issue template
All checks were successful
CI / test (push) Successful in 43s
Completes issue #7 (public mirror setup):
- .githooks/pre-commit: blocks sensitive filenames (.env, *.key, *.pem,
  id_rsa, credentials.json, etc.) and credential content patterns (private
  key headers, AWS keys, GitHub tokens, Stripe secret keys, generic API
  key assignments) from being committed
- .github/ISSUE_TEMPLATE/support_request.md: third issue template for
  usage questions alongside existing bug report and feature request
2026-03-16 11:30:11 -07:00
00a567768b fix: get_config_dir had one extra .parent, resolved to /config not /app/config 2026-03-15 17:14:48 -07:00
1ce283bb79 Merge pull request 'feat: LLM queue optimizer — resource-aware batch scheduler (closes #2)' (#15) from feature/llm-queue-optimizer into main 2026-03-15 16:48:37 -07:00
ab564741f4 fix: _trim_to_letter_end matches full name when no profile set
When _profile is None the fallback pattern \w+ only matched the first
word of a two-word sign-off (e.g. 'Alex' from 'Alex Rivera'), silently
dropping the last name. Switch fallback to \w+(?:\s+\w+)? so a full
first+last sign-off is preserved in no-config environments (CI, first run).
2026-03-15 16:43:27 -07:00
869cb2f197 ci: apt-get update before installing libsqlcipher-dev 2026-03-15 16:37:46 -07:00
27d6fc01fc ci: install libsqlcipher-dev before pip install 2026-03-15 16:36:50 -07:00
e034a07509 ci: re-trigger after actions enabled 2026-03-15 15:54:27 -07:00
2b9a6c8a22 ci: enable forgejo actions 2026-03-15 15:48:35 -07:00
e62548a22e ci: trigger runner 2026-03-15 15:39:45 -07:00
3267a895b0 docs: add Jobgether non-headless Playwright scraper to backlog
Xvfb-based Playwright can bypass Cloudflare Turnstile on jobgether.com.
Live inspection confirmed selectors; deferred pending Xvfb integration.
2026-03-15 11:59:48 -07:00
522534d28e feat: add Jobgether recruiter framing to cover letter generation
When source == "jobgether", build_prompt() injects a recruiter context
note directing the LLM to address the Jobgether recruiter using
"Your client [at {company}] will appreciate..." framing rather than
addressing the employer directly. generate() and task_runner both
thread the is_jobgether flag through automatically.
2026-03-15 09:45:51 -07:00
37119cb332 feat: add Jobgether URL detection and scraper to scrape_url.py 2026-03-15 09:45:50 -07:00
8d9e17d749 feat: filter Jobgether listings via blocklist 2026-03-15 09:45:50 -07:00
4d08e64acf docs: update spec — Jobgether discovery scraper not viable (Cloudflare + robots.txt) 2026-03-15 09:45:50 -07:00
fc6ef88a05 docs: add Jobgether integration implementation plan 2026-03-15 09:45:50 -07:00
952b21377f docs: add cover letter recruiter framing to Jobgether spec 2026-03-15 09:45:50 -07:00
9c87ed1cf2 docs: add Jobgether integration design spec 2026-03-15 09:45:50 -07:00
a1a1141616 Merge pull request 'feat: LLM queue optimizer — resource-aware batch scheduler (closes #2)' (#13) from feature/llm-queue-optimizer into main
Reviewed-on: #13
2026-03-15 05:11:29 -07:00
27d4b0e732 feat: LLM queue optimizer complete — closes #2
Resource-aware batch scheduler for LLM tasks:
- scripts/task_scheduler.py (new): TaskScheduler singleton with VRAM-aware
  batch scheduling, durability, thread-safe singleton, memory safety
- scripts/task_runner.py: submit_task() routes LLM types through scheduler
- scripts/db.py: reset_running_tasks() for durable restart behavior
- app/app.py: _startup() preserves queued tasks on restart
- config/llm.yaml.example: scheduler VRAM budget config documented
- tests/test_task_scheduler.py (new): 24 tests covering all behaviors

Pre-existing failure: test_generate_calls_llm_router (issue #12, unrelated)
2026-03-15 05:01:24 -07:00
95378c106e feat(app): use reset_running_tasks() on startup to preserve queued tasks 2026-03-15 04:57:49 -07:00
07c627cdb0 feat(task_runner): route LLM tasks through scheduler in submit_task()
Replaces the spawn-per-task model for LLM task types with scheduler
routing: cover_letter, company_research, and wizard_generate are now
enqueued via the TaskScheduler singleton for VRAM-aware batching.
Non-LLM tasks (discovery, email_sync, etc.) continue to spawn daemon
threads directly. Adds autouse clean_scheduler fixture to
test_task_runner.py to prevent singleton cross-test contamination.
2026-03-15 04:52:42 -07:00
bcd918fb67 feat(scheduler): add durability — re-queue surviving LLM tasks on startup 2026-03-15 04:24:11 -07:00
207d3816b3 feat(scheduler): implement thread-safe singleton get_scheduler/reset_scheduler 2026-03-15 04:19:23 -07:00
3984a9c743 feat(scheduler): implement scheduler loop and batch worker with VRAM-aware scheduling 2026-03-15 04:14:56 -07:00
4d055f6bcd feat(scheduler): implement enqueue() with depth guard and ghost-row cleanup 2026-03-15 04:05:22 -07:00
28e66001a3 refactor(scheduler): use module-level _get_gpus directly in __init__ 2026-03-15 04:01:01 -07:00
535c0ae9e0 feat(scheduler): implement TaskScheduler.__init__ with budget loading and VRAM detection 2026-03-15 03:32:11 -07:00
3d7f6f7ff1 feat(scheduler): add task_scheduler.py skeleton with constants and TaskSpec 2026-03-15 03:28:43 -07:00
52470759a4 docs(config): add scheduler VRAM budget config to llm.yaml.example 2026-03-15 03:28:26 -07:00
d51066e8c2 refactor(tests): remove unused imports from test_task_scheduler 2026-03-15 03:27:17 -07:00
905db2f147 feat(db): add reset_running_tasks() for durable scheduler restart 2026-03-15 03:22:45 -07:00
eef2478948 docs: add LLM queue optimizer implementation plan
11-task TDD plan across 3 reviewed chunks. Covers:
- reset_running_tasks() db helper
- TaskScheduler skeleton + __init__ + enqueue + loop + workers
- Thread-safe singleton, durability, submit_task routing shim
- app.py startup change + full suite verification
2026-03-14 17:11:49 -07:00
beb1553821 docs: revise queue optimizer spec after review
Addresses 16 review findings across two passes:
- Clarify _active.pop/double-decrement non-issue
- Fix app.py change target (inline SQL, not kill_stuck_tasks)
- Scope durability to LLM types only
- Add _budgets to state table with load logic
- Fix singleton safety explanation (lock, not GIL)
- Ghost row fix: mark dropped tasks failed in DB
- Document static _available_vram as known limitation
- Fix test_llm_tasks_batch_by_type description
- Eliminate circular import via routing split in submit_task()
- Add missing budget warning at construction
2026-03-14 16:46:38 -07:00
61dc2122e4 docs: add LLM queue optimizer design spec
Resource-aware batch scheduler for LLM tasks. Closes #2.
2026-03-14 16:38:47 -07:00
0f80b698ff chore: add .worktrees/ to .gitignore
Prevents worktree directories from being tracked.
2026-03-14 16:30:38 -07:00
097def4bba fix(linkedin): update selectors for 2025 public DOM; surface login-wall limitation in UI
LinkedIn's unauthenticated public profile only exposes name, summary (truncated),
current employer name, and certifications. Past roles, education, and skills are
blurred server-side behind a login wall — not a scraper limitation.

- Update selectors: data-section='summary' (was 'about'), .profile-section-card
  for certs, .visible-list for current experience entry
- Strip login-wall noise injected into summary text after 'see more'
- Skip aria-hidden blurred placeholder experience items
- Add info callout in UI directing users to data export zip for full history
2026-03-13 19:47:21 -07:00
1a50bc1392 chore: update changelog for v0.4.0 release 2026-03-13 11:28:03 -07:00
d1fb4abd56 docs: update backlog with LinkedIn import follow-up items 2026-03-13 11:24:55 -07:00
6c7499752c fix(cloud): use per-user config dir for wizard gate; redirect on invalid session
- app.py: wizard gate now reads get_config_dir()/user.yaml instead of
  hardcoded repo-level config/ — fixes perpetual onboarding loop in
  cloud mode where per-user wizard_complete was never seen
- app.py: page title corrected to "Peregrine"
- cloud_session.py: add get_config_dir() returning per-user config path
  in cloud mode, repo config/ locally
- cloud_session.py: replace st.error() with JS redirect on missing/invalid
  session token so users land on login page instead of error screen
- Home.py, 4_Apply.py, migrate.py: remove remaining AIHawk UI references
2026-03-13 11:24:42 -07:00
42f0e6261c fix(linkedin): conservative settings merge, mkdir guard, split dockerfile playwright layer 2026-03-13 10:58:58 -07:00
1e12da45f1 fix(linkedin): move session state pop before tabs; add rerun after settings merge
- Pop _linkedin_extracted before st.tabs() so tab_builder sees the
  freshly populated _parsed_resume in the same render pass (no extra rerun needed)
- Fix tab label capitalisation: "Build Manually" (capital M) per spec
- Add st.rerun() after LinkedIn merge in Settings so form fields
  refresh immediately to show the newly applied data
2026-03-13 10:55:25 -07:00
b80e4de050 feat(linkedin): install Playwright Chromium in Docker image 2026-03-13 10:44:03 -07:00
7489c1c12a feat(linkedin): add LinkedIn import expander to Settings Resume Profile tab 2026-03-13 10:44:02 -07:00
97ab8b94e5 feat(linkedin): add LinkedIn tab to wizard resume step 2026-03-13 10:43:53 -07:00
bd0e9240eb feat(linkedin): add shared LinkedIn import Streamlit widget 2026-03-13 10:32:23 -07:00
5344dc8e7a feat(linkedin): add staging file parser with re-parse support 2026-03-13 10:18:01 -07:00