LLMs occasionally emit backslash sequences that are valid regex but not valid
JSON (e.g. \s, \d, \p). This caused extract_jd_signals() to fall through to
the exception handler, leaving llm_signals empty. With no LLM signals, the
optimizer fell back to TF-IDF only — which is more conservative and can
legitimately return zero gaps, making the UI appear to say the resume is fine.
Fix: strip bare backslashes not followed by a recognised JSON escape character
(" \ / b f n r t u) before parsing. Preserves \n, \", etc.
Reproduces: cover letter generation concurrent with gap analysis raises the
probability of a slightly malformed LLM response due to model load.
The .forgejo/workflows/ci.yml is the canonical CI definition.
The old .github/workflows/ci.yml was being mirrored to GitHub via
--mirror push, triggering GitHub Actions runs that fail because
FORGEJO_TOKEN and other Forgejo-specific secrets are not set there.
GitHub Actions does not process .forgejo/workflows/ so removing
this file stops the spurious GitHub runs. ISSUE_TEMPLATE and
pull_request_template.md are preserved in .github/.
The import endpoint was doing its own inline PDF/DOCX/ODT extraction
without calling _clean_cid(). Bullet CIDs (127, 149, 183) and other
ATS-reembedded font artifacts were stored raw, surfacing as (cid:127)
in the resume library. Switch to extract_text_from_pdf/docx/odt from
resume_parser.py which already handle two-column layouts and CID cleaning.
date_posted column was added to db.py CREATE TABLE but had no migration
file, so existing user DBs were missing it. The list_jobs endpoint queries
this column, causing 500 errors and empty Apply/Review queues for all
existing cloud users while job_counts (which doesn't touch date_posted)
continued to work — making the home page show correct counts but tabs show
empty data.
Fixes:
- migrations/006_date_posted.sql: ALTER TABLE to add date_posted to existing DBs
- dev_api.py lifespan: on startup in cloud mode, sweep all user DBs in
CLOUD_DATA_ROOT and apply pending migrations — ensures schema changes land
for every user on each deploy, not only on their first post-deploy request
- references_ + job_references tables with CREATE + migration
- Full CRUD: GET/POST /api/references, PATCH/DELETE /api/references/:id
- Link/unlink to jobs: POST/DELETE /api/references/:id/link-job/:job_id
- GET /api/references/for-job/:job_id — linked refs with prep/letter drafts
- POST /api/references/:id/prep-email — LLM drafts heads-up email to send
reference before interview; persisted to job_references.prep_email
- POST /api/references/:id/rec-letter — LLM drafts recommendation letter
reference can edit and send on their letterhead (Paid/BYOK tier)
- ReferencesView.vue: add/edit/delete form, tag system (technical/managerial/
character/academic), inline confirm-before-delete
- Route /references + IdentificationIcon nav link
Adds the cf-text inference service (circuitforge-core) to the LLM
fallback chain as the first option for cover letter generation.
cf-text now exposes /v1/chat/completions (added in cf-core 69a338b),
making it a drop-in openai_compat backend at port 8006.
CF_TEXT_MODEL and CF_TEXT_PORT added to .env.example. Closes#75.
Removes hardcoded _MISSION_SIGNALS and _MISSION_DEFAULTS dicts from
generate_cover_letter.py. Domains and signals are now defined in
config/mission_domains.yaml, which ships with the original 5 domains
(music, animal_welfare, education, social_impact, health) plus 3 new
ones (privacy, accessibility, open_source).
Any key in user.yaml mission_preferences not present in the YAML is
treated as a user-defined domain with no signal detection — custom
note only. Closes#78.
- POST/GET /api/resumes — create and list resumes
- POST /api/resumes/import — import from .txt/.pdf/.docx/.odt/.yaml
- GET/PATCH/DELETE /api/resumes/{id} — CRUD for individual resumes
- POST /api/resumes/{id}/set-default — set default resume
- GET/PATCH /api/jobs/{job_id}/resume — per-job resume association
- Extend approve_resume to optionally save to resume library (save_to_library + resume_name body fields)
- 9 passing tests in tests/test_resumes_api.py
Replaced stale module-level _NOTION_CONNECTED flag (evaluated once at
import time) with live _notion_configured() calls so the dashboard
reflects the actual integration state on each render.
- Sync section subheader/button: "Send to Notion" only when Notion is
configured; otherwise shows "Set up a sync integration" prompt
- Caption and metric label drop "to Notion" when no integration is set
- Closes#16
Replaces the deprecated @app.on_event('startup') decorator with
the asynccontextmanager lifespan pattern. Startup logic (.env load
+ db migration) moves into lifespan(); no shutdown logic needed.
Closes#70
- Rename setup.sh to install.sh (standardise with cf-orch installer pattern)
- Remove hardcoded dev-machine path from activate_git_hooks(); go straight
to .githooks/ core.hooksPath fallback — the absolute path was only valid
on the author's machine and surprised self-hosters
- Add capture_license_key(): optional CFG-XXXX-… prompt after setup_env(),
writes CF_LICENSE_KEY + HEIMDALL_URL into .env when a valid key is entered
- Fix next-steps port: read STREAMLIT_PORT from .env instead of hardcoding
8501 (Docker default is 8502; users who customise the port saw a wrong URL)
- Update .env.example: STREAMLIT_PORT=8502 (Docker default, not bare-metal 8501)
- Fix stale git clone URL in docs/getting-started/installation.md:
git.circuitforge.io → git.opensourcesolarpunk.com/Circuit-Forge/peregrine
- Update all setup.sh references across manage.sh, Makefile, CONTRIBUTING.md,
README.md, and docs/ to install.sh
Closes#71
- task_scheduler: extend LocalScheduler (concrete class), not TaskScheduler
(Protocol); remove unsupported VRAM kwargs from super().__init__()
- dev-api: lazy import db_migrate inside _startup() to avoid worktree
scripts cache issue in test_dev_api_settings.py
- test_task_scheduler: update VRAM-attribute tests to match LocalScheduler
(no _available_vram/_reserved_vram); drop deepest-queue VRAM-gating
ordering assertion (LocalScheduler is FIFO, not priority-gated);
suppress PytestUnhandledThreadExceptionWarning on crash test; fix
budget assertion to not depend on shared pytest tmp dir state
- test_dev_api_settings: patch path functions (_resume_path, _search_prefs_path,
_license_path, _tokens_path, _config_dir) instead of removed module-level
constants; mock _TRAINING_JSONL for finetune status idle test
- test_wizard_tiers: Vue SPA is free tier (issue #20), assert True
- test_wizard_api: patch _search_prefs_path() function, not SEARCH_PREFS_PATH
- test_ui_switcher: free-tier vue preference no longer downgrades to streamlit
API (dev-api.py):
- GET /api/settings/fine-tune/pairs — list pairs from JSONL with index/instruction/source_file
- DELETE /api/settings/fine-tune/pairs/{index} — remove a pair and rewrite JSONL
- POST /api/settings/fine-tune/submit — now queues prepare_training task (replaces UUID stub)
- GET /api/settings/fine-tune/status — returns pairs_count from JSONL (not just DB task)
Store (fineTune.ts):
- TrainingPair interface
- pairs, pairsLoading refs
- loadPairs(), deletePair() actions
Vue (FineTuneView.vue):
- Step 2 shows scrollable pairs list with instruction + source file
- ✕ button on each pair calls deletePair(); list/count update immediately
- loadPairs() called on mount
API additions (dev-api.py):
- GET /api/tasks — list active background tasks
- DELETE /api/tasks/{task_id} — per-task cancel
- POST /api/tasks/kill — kill all stuck tasks
- POST /api/tasks/discovery|email-sync|enrich|score|sync — queue/trigger each workflow
- POST /api/jobs/archive — archive by statuses array
- POST /api/jobs/purge — hard delete by statuses or target (email/non_remote/rescrape)
- POST /api/jobs/add — queue URL imports
- POST /api/jobs/upload-csv — upload CSV with URL column
- GET /api/config/setup-banners — list undismissed onboarding hints
- POST /api/config/setup-banners/{key}/dismiss — dismiss a banner
HomeView.vue:
- 4th WorkflowButton: "Fill Missing Descriptions" (always visible, not gated on enrichment_enabled)
- Danger Zone redesign: scope radio (pending-only vs pending+approved), Archive & reset (primary)
vs Hard purge (secondary), inline confirm dialogs, active task list with per-task cancel,
Kill all stuck button, More Options (email purge / non-remote / wipe+rescrape)
- Setup banners: dismissible onboarding hints pulled from /api/config/setup-banners,
5-second polling for active task list to stay live
app/Home.py:
- Danger Zone redesign: same scope radio + archive/purge with confirm steps
- Background task list with per-task cancel and Kill all stuck button
- More options expander (email purge, non-remote, wipe+rescrape)
- Setup banners section at page bottom
API additions (dev-api.py split across this and next commit):
- /api/jobs/{job_id}/qa GET/PATCH/suggest — Interview Prep answer storage + LLM suggestions
- /api/settings/ui-preference POST — persist streamlit/vue preference to user.yaml
- cancel_task() added to scripts/db.py (per-task cancel for Danger Zone)
Vue / UI:
- AppNav: "⚡ Classic" button to switch back to Streamlit UI (writes cookie + persists to user.yaml)
- ApplyWorkspace: Resume Highlights panel (collapsible skills/domains/keywords with job-match highlighting)
- SettingsView: hide Data tab in cloud mode (showData guard)
- ResumeProfileView: minor improvements
- useApi.ts: error handling improvements
Infra:
- compose.cloud.yml: add api service (uvicorn dev_api running in cloud container)
- docker/web/nginx.conf: proxy /api/* to api service in cloud mode
- README.md: Vue SPA now listed as Free tier feature
- app/app.py: load_env at module level (safe in Docker, fills gaps on bare-metal)
- dev_api.py: load_env in startup handler (avoids test-env pollution)
- requirements.txt: note >= 0.7.0 requirement; TODO tag once cf-core cuts release
db.migration runner deferral: tracked in #43 (Rails-style numbered migrations)
CFOrchClient VRAM wiring: already present in task_scheduler via CF_ORCH_URL env var
When preflight.py adopts a host-running Ollama (or ollama_research) service,
write OLLAMA_HOST (and OLLAMA_RESEARCH_HOST) into .env using host.docker.internal
so LLMRouter's env-var auto-config resolves the correct address from inside the
Docker container without requiring a config/llm.yaml to exist.
#50 Job Review list view — sort + filter controls:
- Sort by best match / newest first / company A-Z (client-side computed)
- Remote-only checkbox filter
- Job count indicator; filters reset on tab switch
- Remote badge on list items
#61 Cover letter generation from approved tab:
- '✨ Draft' button on each approved-list item → /apply/:id
- No extra API call; ApplyWorkspace handles generation from there
#54 Company research modal (all API endpoints already existed):
- CompanyResearchModal.vue: 3-state machine (empty→generating→ready)
polling /research/task every 3s, displays all 7 research sections
(company, leadership, talking points, tech, funding, red flags,
accessibility), copy-to-clipboard for talking points, ↺ Refresh
- InterviewCard: new 'research' emit + '🔍 Research' button for
phone_screen/interviewing/offer stages
- InterviewsView: wires modal with researchJobId/Title/AutoGen state;
auto-opens modal with autoGenerate=true when a job is moved to
phone_screen (mirrors Streamlit behaviour)