Compare commits

..

226 commits

Author SHA1 Message Date
da43578806 fix: get_config_dir had one extra .parent, resolved to /config not /app/config
All checks were successful
CI / test (push) Successful in 43s
2026-03-15 17:14:48 -07:00
5cb66d0bf1 Merge pull request 'feat: LLM queue optimizer — resource-aware batch scheduler (closes #2)' (#15) from feature/llm-queue-optimizer into main
All checks were successful
CI / test (push) Successful in 44s
2026-03-15 16:48:37 -07:00
971336841f fix: _trim_to_letter_end matches full name when no profile set
All checks were successful
CI / test (pull_request) Successful in 42s
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
4a996c2628 ci: apt-get update before installing libsqlcipher-dev
Some checks failed
CI / test (pull_request) Failing after 1m2s
2026-03-15 16:37:46 -07:00
5939ae88eb ci: install libsqlcipher-dev before pip install
Some checks failed
CI / test (pull_request) Failing after 7s
2026-03-15 16:36:50 -07:00
2bf73bbd44 ci: re-trigger after actions enabled
Some checks failed
CI / test (pull_request) Failing after 12s
2026-03-15 15:54:27 -07:00
72c1d4a945 ci: enable forgejo actions 2026-03-15 15:48:35 -07:00
782936bae4 ci: trigger runner 2026-03-15 15:39:45 -07:00
377fde239e docs: add Jobgether non-headless Playwright scraper to backlog
Some checks failed
CI / test (push) Failing after 12s
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
94fd2ea332 feat: add Jobgether recruiter framing to cover letter generation
Some checks failed
CI / test (push) Failing after 32s
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
e6257249cb feat: add Jobgether URL detection and scraper to scrape_url.py 2026-03-15 09:45:50 -07:00
4028667c62 feat: filter Jobgether listings via blocklist 2026-03-15 09:45:50 -07:00
a005397d5d docs: update spec — Jobgether discovery scraper not viable (Cloudflare + robots.txt) 2026-03-15 09:45:50 -07:00
17f7baae3c docs: add Jobgether integration implementation plan 2026-03-15 09:45:50 -07:00
ef9cb29518 docs: add cover letter recruiter framing to Jobgether spec 2026-03-15 09:45:50 -07:00
fe2f27c2a2 docs: add Jobgether integration design spec 2026-03-15 09:45:50 -07:00
e6f0e41de4 Merge pull request 'feat: LLM queue optimizer — resource-aware batch scheduler (closes #2)' (#13) from feature/llm-queue-optimizer into main
Some checks are pending
CI / test (push) Waiting to run
Reviewed-on: #13
2026-03-15 05:11:29 -07:00
22091760bd feat: LLM queue optimizer complete — closes #2
Some checks failed
CI / test (pull_request) Failing after 32s
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
a17ba1e8d8 feat(app): use reset_running_tasks() on startup to preserve queued tasks 2026-03-15 04:57:49 -07:00
1139cd55ec 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
dfd2f0214e feat(scheduler): add durability — re-queue surviving LLM tasks on startup 2026-03-15 04:24:11 -07:00
1d9020c99a feat(scheduler): implement thread-safe singleton get_scheduler/reset_scheduler 2026-03-15 04:19:23 -07:00
84ce68af46 feat(scheduler): implement scheduler loop and batch worker with VRAM-aware scheduling 2026-03-15 04:14:56 -07:00
605e820fa6 feat(scheduler): implement enqueue() with depth guard and ghost-row cleanup 2026-03-15 04:05:22 -07:00
fa780af2f1 refactor(scheduler): use module-level _get_gpus directly in __init__ 2026-03-15 04:01:01 -07:00
cceacdd371 feat(scheduler): implement TaskScheduler.__init__ with budget loading and VRAM detection 2026-03-15 03:32:11 -07:00
0fedf7989e feat(scheduler): add task_scheduler.py skeleton with constants and TaskSpec 2026-03-15 03:28:43 -07:00
b664240340 docs(config): add scheduler VRAM budget config to llm.yaml.example 2026-03-15 03:28:26 -07:00
1f2273f049 refactor(tests): remove unused imports from test_task_scheduler 2026-03-15 03:27:17 -07:00
5ba654e414 feat(db): add reset_running_tasks() for durable scheduler restart 2026-03-15 03:22:45 -07:00
07166325dd 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
7983f3365d 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
9fcfe7daa1 docs: add LLM queue optimizer design spec
Resource-aware batch scheduler for LLM tasks. Closes #2.
2026-03-14 16:38:47 -07:00
397b778217 chore: add .worktrees/ to .gitignore
Prevents worktree directories from being tracked.
2026-03-14 16:30:38 -07:00
f403af4a31 fix(linkedin): update selectors for 2025 public DOM; surface login-wall limitation in UI
Some checks failed
CI / test (push) Has been cancelled
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
100add23bc chore: update changelog for v0.4.0 release
Some checks are pending
CI / test (push) Waiting to run
2026-03-13 11:28:03 -07:00
7d15980bdd docs: update backlog with LinkedIn import follow-up items
Some checks are pending
CI / test (push) Waiting to run
2026-03-13 11:24:55 -07:00
9603d591a3 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
f3617abb6b fix(linkedin): conservative settings merge, mkdir guard, split dockerfile playwright layer 2026-03-13 10:58:58 -07:00
6b59804d35 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
7b9e758861 feat(linkedin): install Playwright Chromium in Docker image 2026-03-13 10:44:03 -07:00
070be6c2e9 feat(linkedin): add LinkedIn import expander to Settings Resume Profile tab 2026-03-13 10:44:02 -07:00
083dff2ec8 feat(linkedin): add LinkedIn tab to wizard resume step 2026-03-13 10:43:53 -07:00
ac1db1ea7f feat(linkedin): add shared LinkedIn import Streamlit widget 2026-03-13 10:32:23 -07:00
260d186c86 feat(linkedin): add staging file parser with re-parse support 2026-03-13 10:18:01 -07:00
04d0a66f21 fix(linkedin): improve scraper error handling, current-job date range, add missing tests 2026-03-13 06:02:03 -07:00
32ed451933 feat(linkedin): add scraper (Playwright + export zip) with URL validation 2026-03-13 01:06:39 -07:00
6c61290218 feat(linkedin): add HTML parser utils with fixture tests 2026-03-13 01:01:05 -07:00
95c5a12196 feat(cloud): add Heimdall tier resolution to cloud_session
Some checks failed
CI / test (push) Has been cancelled
Calls /admin/cloud/resolve after JWT validation to inject the user's
current subscription tier (free/paid/premium/ultra) into session_state
as cloud_tier. Cached 5 minutes via st.cache_data to avoid Heimdall
spam on every Streamlit rerun. Degrades gracefully to free on timeout
or missing token.

New env vars: HEIMDALL_URL, HEIMDALL_ADMIN_TOKEN (added to .env.example
and compose.cloud.yml). HEIMDALL_URL defaults to http://cf-license:8000
for internal Docker network access.

New helper: get_cloud_tier() — returns tier string in cloud mode, "local"
in local-first mode, so pages can distinguish self-hosted from cloud.
2026-03-10 12:31:14 -07:00
cf16af05d2 fix(cloud): extract cf_session cookie by name from X-CF-Session header 2026-03-10 09:22:08 -07:00
ba295cb010 docs: add cloud architecture + cloud-deployment.md
architecture.md: updated Docker Compose table (3 compose files), database
layer (Postgres platform + SQLite-per-user), cloud session middleware,
telemetry system, and cloud design decisions.

cloud-deployment.md (new): full operational runbook — env vars, data root
layout, GDPR deletion, platform DB queries, telemetry, backup/restore,
Caddy routing, demo instance, and onboarding a new app to the cloud.
2026-03-09 23:02:29 -07:00
a893ba6527 feat(cloud): fix backup/restore for cloud mode — SQLCipher encrypt/decrypt
T13: Three fixes:
1. backup.py: _decrypt_db_to_bytes() decrypts SQLCipher DB before archiving
   so the zip is portable to any local Docker install (plain SQLite).
2. backup.py: _encrypt_db_from_bytes() re-encrypts on restore in cloud mode
   so the app can open the restored DB normally.
3. 2_Settings.py: _base_dir uses get_db_path().parent in cloud mode (user's
   per-tenant data dir) instead of the hardcoded app root; db_key wired
   through both create_backup() and restore_backup() calls.

6 new cloud backup tests + 2 unit tests for SQLCipher helpers (pysqlcipher3
mocked — not available in the local conda test env). 419/419 total passing.
2026-03-09 22:41:44 -07:00
f230588291 feat(cloud): Privacy & Telemetry tab in Settings + update_consent()
T11: Add CLOUD_MODE-gated Privacy tab to Settings with full telemetry
consent UI — hard kill switch, anonymous usage toggle, de-identified
content sharing toggle, and time-limited support access grant. All changes
persist to telemetry_consent table via new update_consent() in telemetry.py.

Tab and all DB calls are completely no-op in local mode (CLOUD_MODE=false).
2026-03-09 22:14:22 -07:00
3b9bd5f551 feat(cloud): add compose.cloud.yml and telemetry consent middleware
T8: compose.cloud.yml — multi-tenant cloud stack on port 8505, CLOUD_MODE=true,
per-user encrypted data at /devl/menagerie-data, joins caddy-proxy_caddy-internal
network; .env.example extended with five cloud-only env vars.

T10: app/telemetry.py — log_usage_event() is the ONLY entry point to usage_events
table; hard kill switch (all_disabled) checked before any DB write; complete no-op
in local mode; swallows all exceptions so telemetry never crashes the app;
psycopg2-binary added to requirements.txt. Event calls wired into 4_Apply.py at
cover_letter_generated and job_applied. 5 tests, 413/413 total passing.
2026-03-09 22:10:18 -07:00
357891d335 feat(peregrine): wire cloud_session into pages for multi-tenant db path routing
resolve_session() is a no-op in local mode — no behavior change for existing users.
In cloud mode, injects user-scoped db_path into st.session_state at page load.
2026-03-09 20:22:17 -07:00
6d8f4385d9 feat(peregrine): add cloud_session middleware + SQLCipher get_connection()
cloud_session.py: no-op in local mode; in cloud mode resolves Directus JWT
from X-CF-Session header to per-user db_path in st.session_state.

get_connection() in scripts/db.py: transparent SQLCipher/sqlite3 switch —
uses encrypted driver when CLOUD_MODE=true and key provided, vanilla sqlite3
otherwise. libsqlcipher-dev added to Dockerfile for Docker builds.

6 new cloud_session tests + 1 new get_connection test — 34/34 db tests pass.
2026-03-09 19:43:42 -07:00
9efb6e0432 fix(peregrine): correct port comment in compose.demo.yml, update CLAUDE.md 2026-03-09 15:22:10 -07:00
a3433ab732 chore(peregrine): rename compose.menagerie.yml to compose.demo.yml
Public demo instances moving to demo.circuitforge.tech;
menagerie.circuitforge.tech reserved for cloud-hosted managed instances.
2026-03-09 14:55:38 -07:00
f448dea5a7 docs: update features table to reflect BYOK tier policy
Some checks failed
CI / test (push) Has been cancelled
AI features (cover letter gen, research, interview prep, survey assistant)
are now correctly shown as unlockable at the free tier with any local LLM
or user-supplied API key. Paid tier value prop is managed cloud inference
+ integrations + email sync, not AI feature gating.

Also fixes circuitforge.io → circuitforge.tech throughout.
2026-03-07 22:17:18 -08:00
5f5319d8bf chore: move internal plans to circuitforge-plans repo
Some checks are pending
CI / test (push) Waiting to run
All docs/plans/ files migrated to pyr0ball/circuitforge-plans.
Keeping docs/ for future user-facing documentation.
2026-03-07 15:38:47 -08:00
18efae71e1 chore: expand peregrine .gitleaks.toml allowlists for history scan
Some checks are pending
CI / test (push) Waiting to run
Suppress false positives found during pre-push history scan:
- Path allowlists: docs/plans/*, tests/*, Streamlit app files,
  SearXNG default config, apple_calendar.py placeholder
- Regex allowlists: Unix epoch timestamps, localhost ports,
  555-area-code variants, CFG-* example license key patterns
- All 164 history commits now scan clean
2026-03-07 13:24:18 -08:00
4cead4b74d chore: activate circuitforge-hooks, add peregrine .gitleaks.toml
- Wire core.hooksPath → circuitforge-hooks/hooks via install.sh
- Add .gitleaks.toml extending shared base config with Peregrine-specific
  allowlists (Craigslist/LinkedIn IDs, localhost port patterns)
- Remove .githooks/pre-commit (superseded by gitleaks hook)
- Update setup.sh activate_git_hooks() to call circuitforge-hooks/install.sh
  with .githooks/ as fallback if hooks repo not present
2026-03-07 13:20:52 -08:00
136b9441df docs: circuitforge-hooks implementation plan (8 tasks, TDD) 2026-03-07 12:27:47 -08:00
3441924929 docs: circuitforge-hooks design — gitleaks-based secret + PII scanning
Centralised pre-commit/pre-push hook repo design covering the token leak
root causes: unactivated hooksPath and insufficient regex coverage.
2026-03-07 12:23:54 -08:00
a620c87310 docs: update changelog for v0.3.0 release
Some checks are pending
CI / test (push) Waiting to run
- Add v0.3.0 section: feedback button, BYOK warning, LLM suggest,
  backup/restore, privacy scrub
- Retroactively document v0.2.0 (was in [Unreleased])
- Clear [Unreleased] for future work
2026-03-06 16:04:28 -08:00
4e75a27663 feat: merge feedback-button branch — BYOK warning, PII scrub, LLM suggest, sidebar indicator
Key changes in this branch:
- BYOK cloud backend detection (scripts/byok_guard.py) with full test coverage
- Sidebar amber badge when any cloud LLM backend is active
- Activation warning + acknowledgment required when enabling cloud backend in Settings
- Privacy policy reference doc added
- Suggest search terms, resume keywords, and LLM suggest button in Settings
- Test suite anonymized: real personal data replaced with fictional Alex Rivera
- Full PII scrub from git history (name, email, phone number)
- Digest email parser design doc
- Settings widget crash fixes, Docker service controls, backup/restore script
2026-03-06 16:01:44 -08:00
3a601a1822 test: anonymize real personal data — use fictional Alex Rivera throughout test suite 2026-03-06 15:35:04 -08:00
8992d9c1a7 fix: remove dead byok_cloud_acknowledged scalar key — list is the authority 2026-03-06 15:17:26 -08:00
c8284504fe docs: clarify byok acknowledgment semantics and double-read intent 2026-03-06 15:14:26 -08:00
598cc48f84 feat: byok activation warning — require acknowledgment when enabling cloud LLM 2026-03-06 15:09:43 -08:00
429fc18066 fix: use explicit utf-8 encoding when reading llm.yaml in sidebar 2026-03-06 14:52:22 -08:00
00fc73c91f feat: sidebar cloud LLM indicator — amber badge when any cloud backend active 2026-03-06 14:48:20 -08:00
0c78f19c1e test: add missing base_url edge case + clarify 0.0.0.0 marker intent
Document defensive behavior: openai_compat with no base_url returns True
(cloud) because unknown destination is assumed cloud. Add explanatory
comment to LOCAL_URL_MARKERS for the 0.0.0.0 bind-address case.
2026-03-06 14:43:45 -08:00
9c4250f48e feat: byok_guard — cloud backend detection with full test coverage 2026-03-06 14:40:06 -08:00
4d6cbce91e docs: digest parsers implementation plan (TDD, 6 tasks) 2026-03-05 22:41:40 -08:00
4f5f4180ea docs: add privacy policy reference 2026-03-05 20:59:01 -08:00
d5b0cdc84f feat: add LLM suggest button to Skills & Keywords section
Places a  Suggest button inline with the Skills & Keywords subheader.
On click, calls suggest_resume_keywords() and stores results in session
state. Suggestions render as per-category chip panels (skills, domains,
keywords); clicking a chip appends it to the YAML and removes it from
the panel. A ✕ Clear button dismisses the panel entirely.
2026-03-05 15:13:57 -08:00
50c16bbeb4 feat: wire enhanced suggest_search_terms into Search tab (three-angle excludes)
- Remove old inline _suggest_search_terms (no blocklist/profile awareness)
- Replace with import shim delegating to scripts/suggest_helpers.py
- Call site now loads blocklist.yaml + user.yaml and passes them through
- Update button help text to reflect blocklist, mission values, career background
2026-03-05 15:08:07 -08:00
552f5822bc feat: add suggest_resume_keywords for skills/domains/keywords gap analysis
Replaces NotImplementedError stub with full LLM-backed implementation.
Builds a prompt from the last 3 resume positions plus already-selected
skills/domains/keywords, calls LLMRouter, and returns de-duped suggestions
in all three categories.
2026-03-05 15:00:53 -08:00
50833a5b67 fix: guard mission_preferences values against non-string types in suggest_search_terms 2026-03-05 13:40:53 -08:00
c77ce33173 feat: add suggest_search_terms with three-angle exclude analysis
Replaces NotImplementedError stub with a real LLMRouter-backed implementation
that builds a structured prompt covering blocklist alias expansion, values
misalignment, and role-type filtering, then parses the JSON response into
suggested_titles and suggested_excludes lists.

Moves LLMRouter import to module level so tests can patch it at
scripts.suggest_helpers.LLMRouter.
2026-03-05 13:15:25 -08:00
a865d1583b docs: digest email parser design — LinkedIn/Adzuna/Ladders registry + Avocet bucket 2026-03-05 12:56:53 -08:00
c5e18da572 fix: Settings widget crash, stale setup banners, Docker service controls
- Settings → Search: add-title (+) and Import buttons crashed with
  StreamlitAPIException when writing to _sp_titles_multi after it was
  already instantiated. Fix: pending-key pattern (_sp_titles_pending /
  _sp_locs_pending) applied before widget renders on next pass.

- Home setup banners: fired for email/notion/keywords even when those
  features were already configured. Add 'done' condition callables
  (_email_configured, _notion_configured, _keywords_configured) to
  suppress banners automatically when config files are present.

- Services tab start/stop buttons: docker CLI was unavailable inside
  the container so _docker_available was False and buttons never showed.
  Bind-mount host /usr/bin/docker (ro) + /var/run/docker.sock into the
  app container so it can control sibling containers via DooD pattern.
2026-03-04 12:11:23 -08:00
a3e4e3a493 fix: DEFAULT_DB respects STAGING_DB env var — was ignoring Docker-set path 2026-03-04 11:47:59 -08:00
636694edd1 feat: backup/restore script with multi-instance and legacy support
- create_backup() / restore_backup() / list_backup_contents() public API
- --base-dir PATH flag: targets any instance root (default: this repo)
  --base-dir /devl/job-seeker backs up the legacy Conda install
- _DB_CANDIDATES fallback: data/staging.db (Peregrine) or staging.db root (legacy)
- Manifest records source label (dir name), source_path, created_at, files, includes_db
- Added config/resume_keywords.yaml and config/server.yaml to backup lists
- 21 tests covering create, list, restore, legacy DB path, overwrite, roundtrip
2026-03-04 10:52:51 -08:00
5556817c9a fix: save form data to non-widget state on Next, fix disabled timing, pass page title 2026-03-03 15:17:45 -08:00
bc2c1a9886 fix: llm_backend reads fallback_order, logs tee'd to data/.streamlit.log in Docker 2026-03-03 15:04:18 -08:00
61e656eafc feat: paste/drag-drop image component, remove server-side Playwright capture button 2026-03-03 14:40:47 -08:00
5501c57e64 fix: remove st.rerun() from dialog nav buttons — caused dialog to close on Next/Back 2026-03-03 13:28:26 -08:00
018ca3aae6 fix: pass FORGEJO env vars into app container 2026-03-03 13:17:37 -08:00
063c777cf5 fix: lazy-import playwright in screenshot_page, fix SQLite connection leak in collect_listings 2026-03-03 12:45:39 -08:00
4b06b0e1a2 feat: wire feedback button into app.py sidebar 2026-03-03 12:38:53 -08:00
408755b087 feat: floating feedback button + two-step dialog (Streamlit shell) 2026-03-03 12:20:27 -08:00
9f0befb98b feat: feedback_api — screenshot_page with Playwright (graceful fallback) 2026-03-03 12:14:33 -08:00
bdedeb5305 feat: feedback_api — Forgejo label management + issue filing + attachment upload 2026-03-03 12:09:11 -08:00
cb1131f23c feat: feedback_api — build_issue_body 2026-03-03 12:00:01 -08:00
827182a788 feat: feedback_api — collect_logs + collect_listings 2026-03-03 11:56:35 -08:00
e79de4fa9c chore: remove unused imports from feedback_api (will be re-added in later tasks) 2026-03-03 11:45:14 -08:00
ec22cc8a1f feat: feedback_api — mask_pii + collect_context 2026-03-03 11:43:35 -08:00
00294e3a5b chore: add playwright dep and Forgejo env config for feedback button 2026-03-03 11:38:14 -08:00
25badb7dc3 docs: feedback button implementation plan (8 tasks, TDD) 2026-03-03 11:31:19 -08:00
a8f5445023 docs: feedback button design (floating button, Forgejo integration, PII masking, screenshot support) 2026-03-03 11:22:20 -08:00
bc5f771e43 ci: add GitHub Actions pytest workflow
Some checks are pending
CI / test (push) Waiting to run
2026-03-02 20:44:33 -08:00
3663838d58 docs: add canonical-source banner and CI badge to README 2026-03-02 20:44:23 -08:00
6722692b5c feat: issue templates, PR template, security redirect 2026-03-02 19:35:06 -08:00
4f36af8dca docs: add CONTRIBUTING.md with BSL policy and CLA note 2026-03-02 19:26:25 -08:00
f3fd7b741e docs: add SECURITY.md — responsible disclosure policy 2026-03-02 19:26:23 -08:00
d155472638 feat: setup.sh activates .githooks on clone 2026-03-02 19:17:05 -08:00
d329d3c7ab feat: commit-msg hook enforces conventional commit format 2026-03-02 19:14:31 -08:00
a7fbc42c11 feat: pre-commit hook blocks sensitive files and key patterns 2026-03-02 19:12:14 -08:00
95a4054dc4 docs: public mirror strategy design (GitHub + Codeberg + git hooks) 2026-03-02 18:49:03 -08:00
b5c9aaa364 docs: update tier-system reference with BYOK policy + demo user.yaml
docs/reference/tier-system.md:
  - Rewritten tier table: free tier now described as "AI unlocks with BYOK"
  - New BYOK section explaining the policy and rationale
  - Feature gate table gains BYOK-unlocks? column
  - API reference updated: can_use, tier_label, has_configured_llm with examples
  - "Adding a new feature gate" guide updated to cover BYOK_UNLOCKABLE

demo/config/user.yaml:
  - Reformatted by YAML linter; added dismissed_banners for demo UX
2026-03-02 13:22:10 -08:00
be3bed6e54 feat: BYOK unlocks LLM features regardless of tier
BYOK policy: if a user supplies any LLM backend (local ollama/vllm or
their own API key), they get full access to AI generation features.
Charging for the UI around a service they already pay for is bad UX.

app/wizard/tiers.py:
  - BYOK_UNLOCKABLE frozenset: pure LLM-call features that unlock with
    any configured backend (llm_career_summary, company_research,
    interview_prep, survey_assistant, voice guidelines, etc.)
  - has_configured_llm(): checks llm.yaml for any enabled non-vision
    backend; local + external API keys both count
  - can_use(tier, feature, has_byok=False): BYOK_UNLOCKABLE features
    return True when has_byok=True regardless of tier
  - tier_label(feature, has_byok=False): suppresses lock icon for
    BYOK_UNLOCKABLE features when BYOK is active

Still gated (require CF infrastructure, not just an LLM call):
  llm_keywords_blocklist, email_classifier, model_fine_tuning,
  shared_cover_writer_model, multi_user, all integrations

app/pages/2_Settings.py:
  - Compute _byok = has_configured_llm() once at page load
  - Pass has_byok=_byok to can_use() for _gen_panel_active
  - Update caption to mention BYOK as an alternative to paid tier

app/pages/0_Setup.py:
  - Wizard generation widget passes has_byok=has_configured_llm()
    to can_use() and tier_label()

tests/test_wizard_tiers.py:
  - 6 new BYOK-specific tests covering unlock, non-unlock, and
    label suppression cases
2026-03-02 11:34:36 -08:00
960b8a2e15 feat: DEMO_MODE — isolated public menagerie demo instance
Adds a fully neutered public demo for menagerie.circuitforge.tech/peregrine
that shows the Peregrine UI without exposing any personal data or real LLM inference.

scripts/llm_router.py:
  - Block all inference when DEMO_MODE env var is set (1/true/yes)
  - Raises RuntimeError with a user-friendly "public demo" message

app/app.py:
  - IS_DEMO constant from DEMO_MODE env var
  - Wizard gate bypassed in demo mode (demo/config/user.yaml pre-seeds a fake profile)
  - Demo banner in sidebar: explains read-only status + links to circuitforge.tech

compose.menagerie.yml (new):
  - Separate Docker Compose project (peregrine-demo) on host port 8504
  - Mounts demo/config/ and demo/data/ — isolated from personal instance
  - DEMO_MODE=true, no API keys, no /docs mount
  - Project name: peregrine-demo (run alongside personal instance)

demo/config/user.yaml:
  - Generic "Demo User" profile, wizard_complete=true, no real personal info

demo/config/llm.yaml:
  - All backends disabled (belt-and-suspenders alongside DEMO_MODE block)

demo/data/.gitkeep:
  - staging.db is auto-created on first run, gitignored via demo/data/*.db

.gitignore: add demo/data/*.db

Caddy routes menagerie.circuitforge.tech/peregrine* → 8504 (demo instance).
Personal Peregrine remains on 8502, unchanged.
2026-03-02 11:22:38 -08:00
2e95d7ca28 feat: add reverse-proxy basepath support (Streamlit MIME fix)
- compose.yml: pass STREAMLIT_SERVER_BASE_URL_PATH from .env into container
  Streamlit prefixes all asset URLs with the path so Caddy handle_path routing works.
  Without this, /static/* requests skip the /peregrine* route → 503 text/plain MIME error.
- config/server.yaml.example: document base_url_path + server_port settings
- .gitignore: ignore config/server.yaml (local gitignored instance of server.yaml.example)
- app/pages/2_Settings.py: add Deployment/Server expander under System tab
  Shows active base URL path from env; saves edits to config/server.yaml + .env;
  prompts user to run ./manage.sh restart to apply.

Refs: https://docs.streamlit.io/develop/api-reference/configuration/config.toml#server.baseUrlPath
2026-03-01 22:49:29 -08:00
a75c363f8e feat: discard button — removes email from queue without writing to score file 2026-02-27 15:48:47 -08:00
12248f4fab fix: RerankerAdapter falls back to label name when no LABEL_DESCRIPTIONS entry 2026-02-27 14:54:31 -08:00
60bed6099b feat: label_tool — 9 labels, wildcard Other, InvalidCharacterError fix; sync with avocet canonical 2026-02-27 14:34:24 -08:00
e78e3a36ae feat: add health mission category, trim-to-sign-off, max_tokens cap for cover letters
- _MISSION_SIGNALS: add health category (pharma, clinical, patient care, etc.)
  listed last so music/animals/education/social_impact take priority
- _MISSION_DEFAULTS: health note steers toward people-first framing, not
  industry enthusiasm — focuses on patients navigating rare/invisible journeys
- _trim_to_letter_end(): cuts output at first sign-off + first name to prevent
  fine-tuned models from looping into repetitive garbage after completing letter
- generate(): pass max_tokens=1200 to router (prevents runaway output)
- user.yaml.example: add health + social_impact to mission_preferences,
  add candidate_voice field for per-user voice/personality context
2026-02-27 12:31:06 -08:00
cb99a6a977 feat: dual-GPU DUAL_GPU_MODE complete — ollama/vllm/mixed GPU 1 selection 2026-02-27 06:20:57 -08:00
731e4d1aa2 feat: benchmark_classifier — MODEL_REGISTRY, --list-models, --score, --compare modes 2026-02-27 06:19:32 -08:00
b0ab34dd17 feat: inject DUAL_GPU_MODE sub-profile in Makefile; update manage.sh help 2026-02-27 06:18:34 -08:00
2b0072c8c1 feat: add ollama_research service and update profiles for dual-gpu sub-profiles 2026-02-27 06:16:17 -08:00
b025bb13ee feat: assign ollama_research to GPU 1 in Docker and Podman GPU overlays 2026-02-27 06:16:04 -08:00
f58063264b feat: add DUAL_GPU_MODE default, VRAM warning, and download size report to preflight
- Add _mixed_mode_vram_warning() to flag low VRAM on GPU 1 in mixed mode
- Wire download size report block into main() before closing border line
- Wire mixed-mode VRAM warning into report if triggered
- Write DUAL_GPU_MODE=ollama default to .env for new 2-GPU setups (no override if already set)
- Promote import os to top-level (was local import inside get_cpu_cores)
2026-02-27 00:17:00 -08:00
74455ef10e feat: add _download_size_mb() pure function for preflight size warning 2026-02-27 00:15:26 -08:00
56b8af6bc7 feat: add ollama_research to preflight service table and LLM backend map 2026-02-27 00:14:04 -08:00
c35e4d23e4 test: add failing tests for dual-gpu preflight additions 2026-02-27 00:11:39 -08:00
d82cd43f2a feat: ZeroShotAdapter, GLiClassAdapter, RerankerAdapter with full mock test coverage 2026-02-27 00:10:43 -08:00
1f04f75905 feat: ClassifierAdapter ABC + compute_metrics() with full test coverage 2026-02-27 00:09:45 -08:00
e0bd7d119c feat: add vllm_research backend and update research_fallback_order 2026-02-27 00:09:00 -08:00
c304d9145f feat: add scoring JSONL example and gitignore for benchmark data files 2026-02-26 23:46:29 -08:00
99f0f5b277 feat: add job-seeker-classifiers conda env for HF classifier benchmark 2026-02-26 23:43:41 -08:00
cf98044f6f docs: email classifier benchmark implementation plan — 10 tasks, TDD, 9-model registry 2026-02-26 23:20:04 -08:00
7bbcde3f69 docs: email classifier benchmark design — adapter pattern, 9-model registry, compare+eval modes 2026-02-26 22:56:11 -08:00
f9644385c5 fix: remove lib-resume-builder-aihawk from Docker requirements
The package is never imported in the app — it was pulling torch + CUDA
(~7GB) into the main app container for no reason. AIHawk runs in its own
conda env (aihawk-env) outside Docker per design.
2026-02-26 22:16:28 -08:00
5a4216952f fix: auto-configure git safe.directory in setup.sh for /opt-style installs
Git 2.35.2+ rejects repos where directory owner != current user, which
is the common case when cloned as root into /opt. setup.sh now detects
this and calls git config --global --add safe.directory automatically.
When run via sudo, it writes into SUDO_USER's config rather than root's.
README updated with both fixes: git safe.directory and chown for preflight.
2026-02-26 22:07:39 -08:00
278884d6b8 docs: add install notes for /opt ownership, Podman rootless, Docker group 2026-02-26 21:15:42 -08:00
1abea34752 fix: skip --profile for remote profile; fixes podman-compose compat
podman-compose 1.0.6 has no --profile flag, causing a fatal parse error.
'remote' profile means base services only — no service in compose.yml is
tagged 'remote', so --profile remote was always a no-op with Docker too.
Introduce PROFILE_ARG that only adds --profile for cpu/gpu profiles where
it actually activates optional services.
2026-02-26 21:12:12 -08:00
2c3544ad50 fix: render banner link as clickable page_link instead of italic text 2026-02-26 20:53:54 -08:00
ab5750d5fc fix: install make in setup.sh; guard manage.sh against missing make
setup.sh now installs make (via apt/dnf/pacman/brew) before git and
Docker so that manage.sh commands work out of the box on minimal server
installs. manage.sh adds a preflight guard that catches a missing make
early and redirects the user to ./manage.sh setup. Also fixes the
post-setup next-steps hint to use ./manage.sh instead of bare make.
2026-02-26 20:51:34 -08:00
630b2cad41 feat: show version tag in sidebar footer 2026-02-26 14:39:47 -08:00
22598edff6 feat: multiselect tags for job titles & locations; remove duplicate Notion section; docker detection for services panel
- Job titles and locations: replaced text_area with st.multiselect + + add button + paste-list expander
-  Suggest now populates the titles dropdown (not auto-selected) — user picks what they want
- Suggested exclusions still use click-to-add chip buttons
- Removed duplicate Notion expander from System Settings (handled by Integrations tab)
- Services panel: show host terminal copy-paste command when docker CLI unavailable (app runs inside container)
2026-02-26 14:26:58 -08:00
d593d23fdd fix: add address field to Resume Profile — was hidden, triggering false FILL_IN banner 2026-02-26 14:03:55 -08:00
f57dca265f fix: port drift on restart — down before preflight, read port from .env
Makefile restart target now runs compose down before preflight so ports
are free when preflight assigns them; previously preflight ran first while
the old container still held 8502, causing it to bump to 8503.

manage.sh start/restart/open now read STREAMLIT_PORT from .env instead
of re-running preflight after startup (which would see the live container
and bump the reported port again).
2026-02-26 13:57:12 -08:00
24a108afc7 feat: resume upload in Settings + improved config hints
- Resume Profile tab: upload widget replaces error+stop when YAML missing;
  collapsed "Replace Resume" expander when profile exists; saves parsed
  data and raw text (for LLM context) in one step
- FILL_IN banner with clickable link to Setup wizard when incomplete fields detected
- Ollama not reachable hint references Services section below
- Fine-tune hint clarifies "My Profile tab above" with inference profile names
- vLLM no-models hint links to Fine-Tune tab
2026-02-26 13:53:01 -08:00
29f38cec64 refactor: replace sidebar LLM generate panel with inline field buttons
Removed the dropdown-based sidebar panel in favour of  Generate buttons
placed directly below Career Summary, Voice & Personality, and each Mission
& Values row. Prompts now incorporate the live field value as a draft to
improve, plus resume experience bullets as context for Career Summary.
2026-02-26 13:40:52 -08:00
3cf729bb7a feat: searchable tag UI for skills/domains/keywords
Replace chip-button tag management with st.multiselect backed by bundled
suggestions. Existing user tags are preserved as custom options alongside
the suggestion list. Custom tag input validates through filter_tag() before
adding — rejects URLs, profanity, overlong strings, and bad characters.
Changes auto-save on multiselect interaction; custom tags append on + click.
2026-02-26 13:14:55 -08:00
93bf6b3c6f feat: bundled skills suggestion list and content filter utility
- config/skills_suggestions.yaml: 168 curated tags across skills (77),
  domains (40), keywords (51) covering CS/TAM/ops and common tech roles;
  structured for future community aggregate (paid tier backlog)
- scripts/skills_utils.py: filter_tag() rejects blanks, URLs, profanity,
  overlong strings, disallowed chars, and repeated-char runs;
  load_suggestions() reads bundled YAML per category
2026-02-26 13:09:32 -08:00
e982fa7a8b fix: resume CID glyphs, resume YAML path, PyJWT dep, candidate voice & mission UI
- resume_parser: add _clean_cid() to strip (cid:NNN) glyph refs from ATS PDFs;
  CIDs 127/149/183 become bullets, unknowns are stripped; applied to PDF/DOCX/ODT
- resume YAML: canonicalize plain_text_resume.yaml path to config/ across all
  references (Settings, Apply, Setup, company_research, migrate); was pointing at
  unmounted aihawk/data_folder/ in Docker
- requirements/environment: add PyJWT>=2.8 (was missing; broke Settings page)
- user_profile: add candidate_voice field
- generate_cover_letter: inject candidate_voice into SYSTEM_CONTEXT; add
  social_impact mission signal category (nonprofit, community, equity, etc.)
- Settings: add Voice & Personality textarea to Identity expander; add
  Mission & Values expander with editable fields for all 4 mission categories
- .gitignore: exclude CLAUDE.md, config/plain_text_resume.yaml,
  config/user.yaml.working
- search_profiles: add default profile
2026-02-26 12:32:28 -08:00
7ca20eec42 feat: ODT support, two-column PDF column-split extraction, title/company layout detection hardening 2026-02-26 10:33:28 -08:00
1775c7fa36 fix: harden resume section detection — anchor patterns to full line, expand header synonyms, fix name heuristic for hyphenated/middle-initial names, add parse diagnostics UI 2026-02-26 09:28:31 -08:00
26563a0990 refactor: replace LLM-based resume parser with section regex parser
Primary parse path is now fully deterministic — no LLM, no token limits,
no JSON generation. Handles two-column experience headers, institution-before-
or-after-degree education layouts, and header bleed prevention via
looks_like_header detection.

LLM path retained as optional career_summary enhancement only (1500 chars,
falls back silently). structure_resume() now returns tuple[dict, str].
Tests updated to match the new API.
2026-02-26 07:34:25 -08:00
c8d8434371 fix: resume parser — max_tokens, json-repair fallback, logging, PYTHONUNBUFFERED 2026-02-26 00:00:23 -08:00
af53f0a5eb fix: add python-docx to container requirements 2026-02-25 23:43:30 -08:00
70b385f3fd fix: add /v1 prefix to all license server API paths 2026-02-25 23:35:58 -08:00
6585d9ec75 feat: License tab in Settings (activate/deactivate UI) + startup refresh 2026-02-25 23:08:20 -08:00
fa1f36dc8d feat: wire license.effective_tier into tiers.py; add dev_override priority 2026-02-25 23:05:55 -08:00
7d5a706202 feat: license.py client — verify_local, effective_tier, activate, refresh, report_usage 2026-02-25 22:53:11 -08:00
4da5e0a2a4 fix: GPU detection + pdfplumber + pass GPU env vars into app container
- preflight.py now writes PEREGRINE_GPU_COUNT and PEREGRINE_GPU_NAMES to
  .env so the app container gets GPU info without needing nvidia-smi access
- compose.yml passes PEREGRINE_GPU_COUNT, PEREGRINE_GPU_NAMES, and
  RECOMMENDED_PROFILE as env vars to the app service
- 0_Setup.py _detect_gpus() reads PEREGRINE_GPU_NAMES env var first;
  falls back to nvidia-smi (bare / GPU-passthrough environments)
- 0_Setup.py _suggest_profile() reads RECOMMENDED_PROFILE env var first
- requirements.txt: add pdfplumber (needed for resume PDF parsing)
2026-02-25 21:58:28 -08:00
c7fe1626a7 fix: add app/__init__.py so wizard submodule is importable inside Docker
Without __init__.py, Python treats app/ as a namespace package that
doesn't resolve correctly when running from WORKDIR /app inside the
container. 'from app.wizard.step_hardware import ...' raises
ModuleNotFoundError: No module named 'app.wizard'; 'app' is not a package
2026-02-25 21:41:09 -08:00
1d228b293b fix: stub-port adoption — stubs bind free ports, app routes to external via host.docker.internal
Three inter-related fixes for the service adoption flow:
- preflight: stub_port field — adopted services get a free port for their
  no-op container (avoids binding conflict with external service on real port)
  while update_llm_yaml still uses the real external port for host.docker.internal URLs
- preflight: write_env now uses stub_port (not resolved) for adopted services
  so SEARXNG_PORT etc point to the stub's harmless port, not the occupied one
- preflight: stub containers use sleep infinity + CMD true healthcheck so
  depends_on: service_healthy is satisfied without holding any real port
- Makefile: finetune profile changed from [cpu,single-gpu,dual-gpu] to [finetune]
  so the pytorch/cuda base image is not built during make start
2026-02-25 21:38:23 -08:00
7c62935371 fix: ollama docker_owned=True; finetune gets own profile to avoid build on start
- preflight: ollama was incorrectly marked docker_owned=False — Docker does
  define an ollama service, so external detection now correctly disables it
  via compose.override.yml when host Ollama is already running
- compose.yml: finetune moves from [cpu,single-gpu,dual-gpu] profiles to
  [finetune] profile so it is never built during 'make start' (pytorch/cuda
  base is 3.7GB+ and unnecessary for the UI)
- compose.yml: remove depends_on ollama from finetune — it reaches Ollama
  via OLLAMA_URL env var which works whether Ollama is Docker or host
- Makefile: finetune target uses --profile finetune + compose.gpu.yml overlay
2026-02-25 21:24:33 -08:00
9c1f894446 feat: smart service adoption in preflight — use external services instead of conflicting
preflight.py now detects when a managed service (ollama, vllm, vision,
searxng) is already running on its configured port and adopts it rather
than reassigning or conflicting:

- Generates compose.override.yml disabling Docker containers for adopted
  services (profiles: [_external_] — a profile never passed via --profile)
- Rewrites config/llm.yaml base_url entries to host.docker.internal:<port>
  so the app container can reach host-side services through Docker's
  host-gateway mapping
- compose.yml: adds extra_hosts host.docker.internal:host-gateway to the
  app service (required on Linux; no-op on macOS Docker Desktop)
- .gitignore: excludes compose.override.yml (auto-generated, host-specific)

Only streamlit is non-adoptable and continues to reassign on conflict.
2026-02-25 19:23:02 -08:00
e3fbdd5502 docs: use ./manage.sh setup in quickstart 2026-02-25 17:18:03 -08:00
04915d33be docs: update README — manage.sh CLI reference + correct Forgejo clone URL 2026-02-25 16:59:34 -08:00
ca278d5b41 feat: add manage.sh — single CLI entry point for beta testers 2026-02-25 16:51:30 -08:00
775d54d605 fix: fix dual-gpu port conflict + move GPU config to overlay files
- Remove ollama-gpu service (was colliding with ollama on port 11434)
- Strip inline deploy.resources GPU blocks from vision and vllm
- Add compose.gpu.yml: Docker NVIDIA overlay for ollama (GPU 0),
  vision (GPU 0), vllm (GPU 1), finetune (GPU 0)
- Fix compose.podman-gpu.yml: rename ollama-gpu → ollama to match
  service name after removal of ollama-gpu
- Update Makefile: apply compose.gpu.yml for Docker + GPU profiles
  (was only applying podman-gpu.yml for Podman + GPU profiles)
2026-02-25 16:44:59 -08:00
bcde4c960e feat: wire fine-tune UI end-to-end + harden setup.sh
- setup.sh: replace docker-image-based NVIDIA test with nvidia-ctk validate
  (faster, no 100MB pull, no daemon required); add check_docker_running()
  to auto-start the Docker service on Linux or warn on macOS
- prepare_training_data.py: also scan training_data/uploads/*.{md,txt}
  so web-uploaded letters are included in training data
- task_runner.py: add prepare_training task type (calls build_records +
  write_jsonl inline; reports pair count in task result)
- Settings fine-tune tab: Step 1 accepts .md/.txt uploads; Step 2 Extract
  button submits prepare_training background task + shows status; Step 3
  shows make finetune command + live Ollama model status poller
2026-02-25 16:31:53 -08:00
740b0ea45a feat: containerize fine-tune pipeline (Dockerfile.finetune + make finetune)
- Dockerfile.finetune: PyTorch 2.3/CUDA 12.1 base + unsloth + training stack
- finetune_local.py: auto-register model via Ollama HTTP API after GGUF
  export; path-translate between finetune container mount and Ollama's view;
  update config/llm.yaml automatically; DOCS_DIR env override for Docker
- prepare_training_data.py: DOCS_DIR env override so make prepare-training
  works correctly inside the app container
- compose.yml: add finetune service (cpu/single-gpu/dual-gpu profiles);
  DOCS_DIR=/docs injected into app + finetune containers
- compose.podman-gpu.yml: CDI device override for finetune service
- Makefile: make prepare-training + make finetune targets
2026-02-25 16:22:48 -08:00
cfbe1cdf1a feat: prompt for model weights directory during install
Interactive prompt lets users with split-drive setups point Ollama and
vLLM model dirs at a dedicated storage drive. Reads current .env value
as default so re-runs are idempotent. Skips prompts in non-interactive
(piped) mode. Creates the target directory immediately and updates .env
in-place via portable awk (Linux + macOS). Also simplifies next-steps
output since model paths are now configured at install time.
2026-02-25 16:08:14 -08:00
57a05417dc fix: repair beta installer path for Docker-first deployment
- llm.yaml + example: replace localhost URLs with Docker service names
  (ollama:11434, vllm:8000, vision:8002); replace personal model names
  (alex-cover-writer, llama3.1:8b) with llama3.2:3b
- user.yaml.example: update service hosts to Docker names (ollama, vllm,
  searxng) and searxng port from 8888 (host-mapped) to 8080 (internal)
- wizard step 5: fix hardcoded localhost defaults — wizard runs inside
  Docker, so service name defaults are required for connection tests to pass
- scrapers/companyScraper.py: bundle scraper so Dockerfile COPY succeeds
- setup.sh: remove host Ollama install (conflicts with Docker Ollama on
  port 11434); Docker entrypoint handles model download automatically
- README + setup.sh banner: add Circuit Forge mission statement
2026-02-25 16:03:10 -08:00
4f67be4020 feat: add Ollama install + service start + model pull to setup.sh 2026-02-25 15:42:56 -08:00
7b53e6fd75 feat: Podman support — auto-detect COMPOSE, CDI GPU override, podman-compose in setup.sh 2026-02-25 15:36:36 -08:00
6be0566335 docs: fix license server paths — dev under CircuitForge/, live at /devl/ 2026-02-25 15:28:32 -08:00
1e6950893a docs: CircuitForge license server implementation plan (11 tasks) 2026-02-25 15:27:39 -08:00
b40dda3b91 docs: CircuitForge license server design doc
RS256 JWT, FastAPI + SQLite, multi-product schema, offline-capable
client integration. Covers server, Peregrine client, deployment,
admin workflow, and testing strategy.
2026-02-25 15:21:07 -08:00
8ceb1d2ebc docs: mark cover letter refinement complete in backlog + changelog 2026-02-25 14:44:50 -08:00
7fab2a0cd3 feat: cover letter iterative refinement — feedback UI + backend params
- generate() accepts previous_result + feedback; appends both to LLM prompt
- task_runner cover_letter handler parses params JSON, passes fields through
- Apply Workspace: "Refine with Feedback" expander with text area + Regenerate
  button; only shown when a draft exists; clears feedback after submitting
- 8 new tests (TestGenerateRefinement + TestTaskRunnerCoverLetterParams)
2026-02-25 14:44:20 -08:00
37ce9fb1f8 docs: finalise Circuit Forge product suite naming + product brief 2026-02-25 14:16:56 -08:00
76e97bd1cb docs: backlog — Circuit Forge product expansion (heinous tasks platform) 2026-02-25 14:02:07 -08:00
c1381c65ba docs: mark email sync test checklist complete 2026-02-25 13:56:55 -08:00
f3cfd258c6 test: complete email sync test coverage — 44 new tests across all checklist sections 2026-02-25 13:55:55 -08:00
3076f051d7 chore: mkdocs deps, CHANGELOG, remove dead Resume Editor page, backlog gap items 2026-02-25 13:51:13 -08:00
ddabf85a3d docs: LICENSE-MIT + LICENSE-BSL + updated README for 7-step wizard and current feature set 2026-02-25 12:06:28 -08:00
0ba27c3939 docs: mkdocs wiki — installation, user guide, developer guide, reference
Adds a full MkDocs documentation site under docs/ with Material theme.

Getting Started: installation walkthrough, 7-step first-run wizard guide,
Docker Compose profile reference with GPU memory guidance and preflight.py
description.

User Guide: job discovery (search profiles, custom boards, enrichment),
job review (sorting, match scores, batch actions), apply workspace (cover
letter gen, PDF export, mark applied), interviews (kanban stages, company
research auto-trigger, survey assistant), email sync (IMAP, Gmail App
Password, classification labels, stage auto-updates), integrations (all 13
drivers with tier requirements), settings (every tab documented).

Developer Guide: contributing (dev env setup, code style, branch naming, PR
checklist), architecture (ASCII layer diagram, design decisions), adding
scrapers (full scrape() interface, registration, search profile config,
test patterns), adding integrations (IntegrationBase full interface, auto-
discovery, tier gating, test patterns), testing (patterns, fixtures, what
not to test).

Reference: tier system (full FEATURES table, can_use/tier_label API, dev
override, adding gates), LLM router (backend types, complete() signature,
fallback chains, vision routing, __auto__ resolution, adding backends),
config files (every file with field-level docs and gitignore status).

Also adds CONTRIBUTING.md at repo root pointing to the docs site.
2026-02-25 12:05:49 -08:00
ad7a56dca5 docs: backlog — Ultra tier managed applications concept 2026-02-25 11:40:55 -08:00
2163f428dc feat: Integrations tab in Settings — connect/test/disconnect all 12 integration drivers 2026-02-25 11:30:44 -08:00
bbb3eda747 refactor: move HF token to Developer tab — hidden from standard user UI 2026-02-25 11:04:13 -08:00
a0164814e9 feat: expanded first-run wizard — complete implementation
13-task implementation covering:
- UserProfile wizard fields (wizard_complete, wizard_step, tier, dev_tier_override,
  dismissed_banners, effective_tier) + params column in background_tasks
- Tier system: FEATURES gate, can_use(), tier_label() (app/wizard/tiers.py)
- Six pure validate() step modules (hardware, tier, identity, resume, inference, search)
- Resume parser: PDF (pdfplumber) + DOCX (python-docx) extraction + LLM structuring
- Integration base class + auto-discovery registry (scripts/integrations/)
- 13 integration drivers (Notion, Google Sheets, Airtable, Google Drive, Dropbox,
  OneDrive, MEGA, Nextcloud, Google Calendar, Apple Calendar, Slack, Discord,
  Home Assistant) + config/integrations/*.yaml.example files
- wizard_generate task type with 8 LLM generation sections + iterative refinement
  (previous_result + feedback support)
- step_integrations module: validate(), get_available(), is_connected()
- Wizard orchestrator rewrite (0_Setup.py): 7 steps, crash recovery, LLM polling
- app.py gate: checks wizard_complete flag in addition to file existence
- Home page: 13 dismissible contextual setup banners (wizard_complete-gated)
- Settings: Developer tab — tier override selectbox + wizard reset button

219 tests passing.
2026-02-25 10:54:24 -08:00
ea4f6a9160 feat: Developer tab in Settings — tier override + wizard reset button 2026-02-25 10:50:14 -08:00
928825b9b9 feat: dismissible setup banners on Home page (13 contextual prompts) 2026-02-25 09:53:34 -08:00
2a09b40a1d feat: app.py checks wizard_complete flag to gate main app 2026-02-25 09:43:53 -08:00
daf8e4a382 feat: wizard orchestrator — 7 steps, LLM generation polling, crash recovery
Replaces the old 5-step wizard with a 7-step orchestrator that uses the
step modules built in Tasks 2-8. Steps 1-6 are mandatory (hardware, tier,
identity, resume, inference, search); step 7 (integrations) is optional.
Each Next click validates, writes wizard_step to user.yaml for crash recovery,
and resumes at the correct step on page reload. LLM generation buttons
submit wizard_generate tasks and poll via @st.fragment(run_every=3). Finish
sets wizard_complete=True, removes wizard_step, and calls apply_service_urls.

Adds tests/test_wizard_flow.py (7 tests) covering validate() chain, yaml
persistence helpers, and wizard state inference.
2026-02-25 09:10:51 -08:00
6b093522bf feat: step_integrations module with validate() + tier-filtered available list 2026-02-25 08:35:16 -08:00
eb0e7883b8 docs: backlog — cover letter iterative refinement feedback loop 2026-02-25 08:30:24 -08:00
9fdb95e17f feat: wizard_generate — feedback + previous_result support for iterative refinement 2026-02-25 08:29:56 -08:00
6156aebd3a feat: wizard_generate task type — 8 LLM generation sections 2026-02-25 08:25:17 -08:00
445917cbd6 docs: backlog — Podman support + FastAPI migration path 2026-02-25 08:22:24 -08:00
2dd331cd59 feat: 13 integration implementations + config examples
Add all 13 integration modules (Notion, Google Drive, Google Sheets,
Airtable, Dropbox, OneDrive, MEGA, Nextcloud, Google Calendar, Apple
Calendar/CalDAV, Slack, Discord, Home Assistant) with fields(), connect(),
and test() implementations. Add config/integrations/*.yaml.example files
and gitignore rules for live config files. Add 5 new registry/schema
tests bringing total to 193 passing.
2026-02-25 08:18:45 -08:00
f67eaab7de feat: integration base class + auto-discovery registry 2026-02-25 08:13:14 -08:00
c7e4749fc6 feat: resume parser — PDF/DOCX extraction + LLM structuring 2026-02-25 08:04:48 -08:00
2a7f88a771 feat: wizard step validate() functions — all six mandatory steps 2026-02-25 08:00:18 -08:00
df58213f4d feat: tier system with FEATURES gate + can_use() + tier_label() 2026-02-25 07:55:47 -08:00
e2b5b26689 feat: wizard fields in UserProfile + params column in background_tasks
- Add tier, dev_tier_override, wizard_complete, wizard_step, dismissed_banners
  fields to UserProfile with defaults and effective_tier property
- Add params TEXT column to background_tasks table (CREATE + migration)
- Update insert_task() to accept params with params-aware dedup logic
- Update submit_task() and _run_task() to thread params through
- Add test_wizard_defaults, test_effective_tier_override,
  test_effective_tier_no_override, and test_insert_task_with_params
2026-02-25 07:27:14 -08:00
873d035de6 docs: expanded wizard implementation plan — 13 tasks, TDD throughout 2026-02-25 06:29:23 -08:00
ec2f35380a docs: expanded first-run wizard design
Architecture: wizard module system, mandatory 6-step flow, optional
home banners, tier gating (free/paid/premium + dev_tier_override),
resume upload/parse/builder, LLM generation via background tasks,
integrations registry pattern with 14 v1 services.
2026-02-24 21:30:05 -08:00
3d3f81c252 chore: add backlog.md + gitignore config/.backup-* dirs 2026-02-24 20:54:12 -08:00
236db81ed3 feat: startup preflight — port collision avoidance + resource checks
scripts/preflight.py (stdlib-only, no psutil):
- Port probing: owned services auto-reassign to next free port; external
  services (Ollama) show ✓ reachable / ⚠ not responding
- System resources: CPU cores, RAM (total + available), GPU VRAM via
  nvidia-smi; works on Linux + macOS
- Profile recommendation: remote / cpu / single-gpu / dual-gpu
- vLLM KV cache offload: calculates CPU_OFFLOAD_GB when VRAM < 10 GB
  free and RAM headroom > 4 GB (uses up to 25% of available headroom)
- Writes resolved values to .env for docker compose; single-service mode
  (--service streamlit) for scripted port queries
- Exit 0 unless an owned port genuinely can't be resolved

scripts/manage-ui.sh:
- Calls preflight.py --service streamlit before bind; falls back to
  pure-bash port scan if Python/yaml unavailable

compose.yml:
- vllm command: adds --cpu-offload-gb ${CPU_OFFLOAD_GB:-0}

Makefile:
- start / restart depend on preflight target
- PYTHON variable for env portability
- test target uses PYTHON variable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 20:36:16 -08:00
3eeccd7a33 feat: migration tool + portable startup scripts
scripts/migrate.py:
- dry-run by default; --apply writes files; --copy-db migrates staging.db
- generates config/user.yaml from source repo's resume + cover letter scripts
- copies gitignored configs (notion, email, adzuna, craigslist, search profiles,
  resume keywords, blocklist, aihawk resume)
- merges fine-tuned model name from source llm.yaml into dest llm.yaml

scripts/manage-ui.sh:
- STREAMLIT_BIN no longer hardcoded; auto-resolves via conda env or PATH;
  override with STREAMLIT_BIN env var

scripts/manage-vllm.sh:
- VLLM_BIN and MODEL_DIR now read from env vars with portable defaults

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 20:25:54 -08:00
84eb647348 feat: LGBTQIA+ focus + Phase 2/3 audit fixes
LGBTQIA+ inclusion section in research briefs:
- user_profile.py: add candidate_lgbtq_focus bool accessor
- user.yaml.example: add candidate_lgbtq_focus flag (default false)
- company_research.py: gate new LGBTQIA+ section behind flag; section
  count now dynamic (7 base + 1 per opt-in section, max 9)
- 2_Settings.py: add "Research Brief Preferences" expander with
  checkboxes for both accessibility and LGBTQIA+ focus flags;
  mission_preferences now round-trips through save (no silent drop)

Phase 2 fixes:
- manage-vllm.sh: MODEL_DIR and VLLM_BIN now read from env vars
  (VLLM_MODELS_DIR, VLLM_BIN) with portable defaults
- search_profiles.yaml: replace personal CS/TAM/Bay Area profiles
  with a documented generic starter profile

Phase 3 fix:
- llm.yaml: rename alex-cover-writer:latest → llama3.2:3b with
  inline comment for users to substitute their fine-tuned model;
  fix model-exclusion comment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 20:02:03 -08:00
40bd297b14 fix: remove hardcoded personal values — Phase 1 audit findings
- 3_Resume_Editor.py: replace "Alex's" in docstring and caption
- user_profile.py: expose mission_preferences and candidate_accessibility_focus
- user.yaml.example: add mission_preferences section + candidate_accessibility_focus flag
- generate_cover_letter.py: build _MISSION_NOTES from user profile instead of
  hardcoded personal passion notes; falls back to generic defaults when not set
- company_research.py: gate "Inclusion & Accessibility" section behind
  candidate_accessibility_focus flag; section count adjusts (7 or 8) accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 19:57:03 -08:00
cbaa7e019e feat: add cross-platform dependency installer and Makefile for Linux/macOS
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 19:47:06 -08:00
48c957da64 feat: complete generalization — smoke tests, README, all personal refs extracted
- UserProfile class drives all personal data
- First-run wizard gates app until user.yaml exists
- Docker Compose stack: remote/cpu/single-gpu/dual-gpu profiles
- Vision service containerized (single-gpu/dual-gpu)
- All Alex/Library references removed from app and scripts
- Circuit Forge LLC / Peregrine branding throughout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 19:41:09 -08:00
9931f811ac feat: add vision service to compose stack and fine-tune wizard tab to Settings
- Add moondream2 vision service to compose.yml (single-gpu + dual-gpu profiles)
- Create scripts/vision_service/Dockerfile for the vision container
- Add VISION_PORT, VISION_MODEL, VISION_REVISION vars to .env.example
- Add Vision Service entry to SERVICES list in Settings (hidden unless gpu profile active)
- Add Fine-Tune Wizard tab (Task 10) to Settings with 3-step upload→preview→train flow
- Tab is always rendered; shows info message when non-GPU profile is active

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 19:37:55 -08:00
678460def2 feat: services tab uses docker compose commands and SSL-aware health checks
Replace hardcoded systemd/shell-script service commands with docker compose
profile-aware commands. Add inference_profile-based filtering (hidden flag
removes Ollama on remote profile, vLLM unless dual-gpu). Replace TCP socket
health check with HTTP-based _port_open() that accepts host/ssl/verify params
for remote/TLS-terminated service support.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 19:34:44 -08:00
0290c6fa17 feat: add Docker Compose stack with remote/cpu/single-gpu/dual-gpu profiles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 19:31:57 -08:00
381aabe358 fix: persist API keys to .env and write notion.yaml with field_map defaults in wizard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 19:24:51 -08:00
05fe5999ae feat: first-run setup wizard gates app until user.yaml is created
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 19:20:35 -08:00
5b13b3126e feat: add My Profile tab to Settings with full user.yaml editing and URL auto-generation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 19:16:31 -08:00
3855a2c7a1 test: add ollama_research URL assertion to llm config generation test 2026-02-24 19:14:33 -08:00
e575c66d53 feat: auto-generate llm.yaml base_url values from user profile services config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 19:10:54 -08:00
6d763d4a1e feat: extract hard-coded personal references from all app pages via UserProfile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 19:00:47 -08:00
2b5ee80ca8 fix: thread searxng URL through research functions via _SEARXNG_URL constant
- Add module-level _SEARXNG_URL derived from UserProfile.searxng_url (or default localhost:8888)
- Update all _searxng_running() call sites to pass _SEARXNG_URL explicitly
- Replace hardcoded "http://localhost:8888/" in _scrape_company() with _SEARXNG_URL + "/"
- Replace hardcoded "http://localhost:8888/search" in _run_search_query() with f"{_SEARXNG_URL}/search"
- Guard _profile.name.split() against empty string in finetune_local.py OLLAMA_NAME

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 18:52:10 -08:00
bc94a92681 feat: extract hard-coded personal references from all scripts via UserProfile
Replace hard-coded paths (/Library/Documents/JobSearch), names (Alex Rivera),
NDA sets (_NDA_COMPANIES), and the scraper path with UserProfile-driven lookups.
Update tests to be profile-agnostic (no user.yaml in peregrine config dir).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 18:45:39 -08:00
83ce120666 feat: add UserProfile class with service URL generation and NDA helpers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 18:29:45 -08:00
2 changed files with 0 additions and 110 deletions

View file

@ -1,84 +0,0 @@
#!/usr/bin/env bash
# .githooks/pre-commit — blocks sensitive files and credential patterns from being committed
set -euo pipefail
RED='\033[0;31m'; YELLOW='\033[1;33m'; BOLD='\033[1m'; NC='\033[0m'
BLOCKED=0
STAGED=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null)
if [[ -z "$STAGED" ]]; then
exit 0
fi
# ── Blocked filenames ──────────────────────────────────────────────────────────
BLOCKED_FILES=(
".env"
".env.local"
".env.production"
".env.staging"
"*.pem"
"*.key"
"*.p12"
"*.pfx"
"id_rsa"
"id_ecdsa"
"id_ed25519"
"id_dsa"
"*.ppk"
"secrets.yml"
"secrets.yaml"
"credentials.json"
"service-account*.json"
"*.keystore"
"htpasswd"
".htpasswd"
)
while IFS= read -r file; do
filename="$(basename "$file")"
for pattern in "${BLOCKED_FILES[@]}"; do
# shellcheck disable=SC2254
case "$filename" in
$pattern)
echo -e "${RED}BLOCKED:${NC} ${BOLD}$file${NC} matches blocked filename pattern '${YELLOW}$pattern${NC}'"
BLOCKED=1
;;
esac
done
done <<< "$STAGED"
# ── Blocked content patterns ───────────────────────────────────────────────────
declare -A CONTENT_PATTERNS=(
["RSA/EC private key header"]="-----BEGIN (RSA|EC|DSA|OPENSSH) PRIVATE KEY"
["AWS access key"]="AKIA[0-9A-Z]{16}"
["GitHub token"]="ghp_[A-Za-z0-9]{36}"
["Generic API key assignment"]="(api_key|API_KEY|secret_key|SECRET_KEY)\s*=\s*['\"][A-Za-z0-9_\-]{16,}"
["Stripe secret key"]="sk_(live|test)_[A-Za-z0-9]{24,}"
["Forgejo/Gitea token (40 hex chars)"]="[a-f0-9]{40}"
)
while IFS= read -r file; do
# Skip binary files
if git diff --cached -- "$file" | grep -qP "^\+.*\x00"; then
continue
fi
for label in "${!CONTENT_PATTERNS[@]}"; do
pattern="${CONTENT_PATTERNS[$label]}"
matches=$(git diff --cached -- "$file" | grep "^+" | grep -cP "$pattern" 2>/dev/null || true)
if [[ "$matches" -gt 0 ]]; then
echo -e "${RED}BLOCKED:${NC} ${BOLD}$file${NC} contains pattern matching '${YELLOW}$label${NC}'"
BLOCKED=1
fi
done
done <<< "$STAGED"
# ── Result ─────────────────────────────────────────────────────────────────────
if [[ "$BLOCKED" -eq 1 ]]; then
echo ""
echo -e "${RED}Commit rejected.${NC} Remove sensitive files/content before committing."
echo -e "To bypass in an emergency: ${YELLOW}git commit --no-verify${NC} (use with extreme caution)"
exit 1
fi
exit 0

View file

@ -1,26 +0,0 @@
---
name: Support Request
about: Ask a question or get help using Peregrine
title: '[Support] '
labels: question
assignees: ''
---
## What are you trying to do?
<!-- Describe what you're trying to accomplish -->
## What have you tried?
<!-- Steps you've already taken, docs you've read, etc. -->
## Environment
- OS: <!-- e.g. Ubuntu 22.04, macOS 14 -->
- Install method: <!-- Docker / Podman / source -->
- Peregrine version: <!-- run `./manage.sh status` or check the UI footer -->
- LLM backend: <!-- Ollama / vLLM / OpenAI / other -->
## Logs or screenshots
<!-- Paste relevant output from `./manage.sh logs` or attach a screenshot -->