pysqlcipher3 is a C extension that links against libsqlcipher — not
present on ubuntu-latest by default. Mirrors what the Dockerfile already
does. backup.py uses it for encrypted DB export but never in CI tests.
Matches snipe's pattern — pip resolves the git+ VCS dep more reliably
when it's pre-installed as a separate step rather than mixed into a
full requirements.txt solve.
Two workflow fixes:
- ci.yml: add explicit `pip install ruff` before lint step; also add
freeze/** to push trigger so RC branches run CI
- mirror.yml: rename GITHUB_MIRROR_TOKEN → GH_MIRROR_TOKEN to match
org-level secret; Forgejo reserves the GITHUB_* prefix so that name
was silently shadowed, causing mirror pushes to fail with auth errors
Bug fixes (filed as #125–#128):
- Wizard step 7 read data.titles instead of data.search.titles — user-entered
job titles and locations were silently dropped on every wizard run (#125)
- GET /api/settings/search returned "titles" key but store expected "job_titles" —
Settings → Search Prefs always showed empty even when data existed (#126)
- remote_only preference not persisted during wizard setup (#127)
- apply-to-profile didn't set default_resume_id in user.yaml, so future
Resume Profile saves never synced back to the library entry (#128)
Also:
- Wizard step headings corrected (off-by-one after Training step was inserted)
- Ollama host in wizard inference step now reads from saved wizard state
- Resume upload during wizard now creates a library entry and sets it as default
Docs:
- New: docs/user-guide/daily-workflow.md — end-to-end daily usage guide
- Updated: docs/user-guide/settings.md — rewritten for Vue SPA (was Streamlit)
- mkdocs.yml nav: Daily Workflow added as first User Guide entry
GUI help links:
- web/src/composables/useDocsUrl.ts — shared docs base URL composable
- Home: "Daily Workflow guide ↗" link in subtitle
- Job Review: "? Docs" link in title row
- Resume Library: "? Help" link in header
- Settings → Resume Profile: "? Help" link in page header
- Settings → Search Prefs: "? Help" link in page header
- Reorder PROFILES in step_hardware.py, _WIZARD_PROFILES in dev-api.py,
and <option> elements in WizardHardwareStep.vue: cpu → single-gpu → dual-gpu → cf-orch → remote
- _suggest_profile() now defaults to "cpu" instead of "remote" when no local GPUs detected
- Update no-GPU hint text to remove "Remote" from suggested options
- Add nvidia GPU device reservation to compose.wizard-test.yml so the
wizard test instance can run nvidia-smi and detect host GPUs
- Switch wizard-test compose to use ghcr.io/circuitforgellc/peregrine:latest
(same image as main compose, avoids stale peregrine-api tag drift)
- Replace stale Streamlit Dockerfile with self-contained release build
(uvicorn/FastAPI; Streamlit removed in #104)
- cf-orch BSL client installed via BuildKit secret in release CI;
community builds skip it gracefully and fall back to local backends
- compose.yml api build now uses single-repo context (context: .)
so self-hosters can build without sibling repo setup
- Add image: tags to api + web services in compose.yml and compose.demo.yml
so docker compose pull works for pre-built images
- Enable Docker push in release.yml: api + web to GHCR on v* tags
(was disabled pending BSL registry policy — cf-agents#3 resolved)
- cloud image (compose.cloud.yml / Dockerfile.cfcore) unchanged:
never published, built on Heimdall with sibling repos available
- .dockerignore: add plain_text_resume.yaml and adzuna.yaml
- Fix cf_text base_url (was port 8006/cf-musicgen, corrected to 8008/cf-text)
- Add cf_orch blocks to cf_text, ollama, ollama_research, vllm_research backends
- Fix ollama_research base_url to host.docker.internal:11435 (was Docker service name)
- Promote cf_text to top of research_fallback_order
- Add cf_text backend to llm.cloud.yaml with cf_orch block
- Wire _RL_WIZARD rate limit to wizard_ai_interview endpoint (closes TODO from #122)
Closes: #122
Each importlib.reload(dev_api) re-applies @limiter.limit() decorators to
the shared slowapi Limiter singleton, accumulating stale registrations in
_route_limits. One real HTTP request then triggered N limit-checks (N =
reload count), exhausting per-hour budgets prematurely.
Fix: conftest.py autouse fixture resets both _storage and _route_limits
before each test, giving a clean slate regardless of prior reloads.
Also updates test_dev_api_prep.py client fixture to use monkeypatch to
clear DEMO_MODE + importlib.reload to get a fresh IS_DEMO module state
(prevents 403 bleed from test_demo_guard.py tests running first).
All 842 tests passing.
Issue #120 — sync status panel in DataView:
- Add SyncStore (web/src/stores/settings/sync.ts) to track last-sync
timestamp, in-progress state, and error message for profile/preferences
- Extend DataView with a sync status section: last synced time, refresh
button, error display, and per-section progress indicators
Issue #118 — bugbot Forgejo token fallback:
- scripts/feedback_api.py: try FORGEJO_BOT_TOKEN first, then fall back to
FORGEJO_TOKEN so ops can provision a dedicated cf-bugbot account without
breaking existing single-token installs
Add FORGEJO_BOT_TOKEN and LLM_RATE_* env var documentation to .env.example
Closes: #120
Closes: #118
Add scripts/rate_limit.py with cloud-aware key function:
- In cloud mode, extracts user_id from _request_db ContextVar path (part[-3])
so each cloud user has their own rate limit bucket
- In demo mode, returns unique per-request key to disable limiting entirely
(_demo_guard handles write-blocking; rate limiting would block the demo UX)
- Falls back to client IP for local/self-hosted installs
Wire limiter to 4 endpoints with conservative per-user limits:
- POST /generate/cover-letter: 20/hour
- POST /research/run: 10/hour
- POST /qa/suggest: 60/hour
- POST /survey/analyze: 30/hour
Add _demo_guard() to generate_research and suggest_qa_answer (was missing).
Fix pre-existing silent except in suggest_qa_answer: was bare except pass,
now logs warning with exc_info.
Add _RL_WIZARD placeholder constant with TODO to wire to wizard/ai/interview
after feat/77 merges (declared but intentionally not applied yet to avoid
false sense of security — comment makes the gap explicit).
18 tests covering cloud user isolation, demo bypass, IP fallback, all 4
endpoints returning 429 on excess, retry_after header, and demo guard.
Closes: #122
- Add keepChatting() action to aiInterview store; replace direct store.complete = false
mutation in WizardAIView template with store.keepChatting()
- Add skip() action wrapping SKIP_SIGNAL constant; replace magic string store.send('skip')
with store.skip()
- Fix skip button disabled condition to include || store.complete (was always enabled
when wizard was complete, allowing spurious skip after finalize)
- Add _persist() call after user bubble append in send() so localStorage draft is
written before the async fetch — prevents stale draft on browser refresh during
slow LLM call
- Fix @click="store.startOver" → @click="store.startOver()" (missing parentheses)
- Add 2 tests: skip() sends SKIP_SIGNAL, keepChatting() clears complete without reset
- Remove 'ultra' from Tier type in appConfig.ts (violates no-ultra-tier policy)
- Add MyProfileView wizard callout banner with tier-aware unlock/upgrade CTAs
- Add clarifying comment on wizard route guard in router/index.ts
Closes: #77
Humans own design, architecture, code review, testing, and
verification. LLMs are part of our development workflow.
Links to circuitforge.tech/positions for our full position.
ruff --fix removed the TaskSpec import as unused within the module,
but it is part of the public API — tests import it from scripts.task_scheduler
rather than reaching into circuitforge_core directly.
Add # noqa: F401 to protect intentional re-exports from future auto-fix.
Forgejo reserves the GITHUB_* prefix for secret names — creating a secret
called GITHUB_MIRROR_TOKEN returns 'invalid secret name'.
Also rename the GITHUB_TOKEN step env var to GH_MIRROR_PAT to avoid
collision with the built-in Forgejo Actions context variable.
- Add pyproject.toml with ruff per-file-ignores:
- Exclude deprecated app/ Streamlit dir entirely
- Suppress E702 in dev-api.py (intentional compact Pydantic models)
- Suppress E402 in finetune_local.py (conditional ML imports after CUDA check)
- Suppress F841/E741/E702 in tests/ (mock-patch capture pattern)
- Remove unused db_path_obj assignment in dev-api.py:760
- Add # noqa: E402 to documented mid-file imports in dev-api.py
- Rename ambiguous l variable to line/lbl in finetune_local.py + label_tool.py
InterviewCard: remove erroneous *100 multiplier from scoreClass and
scoreLabel — match_score is stored as 0-100 in the DB, not 0-1. This
was producing scores like '1490%' for jobs with a 14.9 raw score.
peregrine.css: define --color-hover token for light (rgba(0,0,0,0.06))
and dark (rgba(255,255,255,0.07)). Was undefined, leaving hover states
on InterviewCard, InterviewsView, ReferencesView, ContactsView silent.
InterviewCard + InterviewsView: replace var(--color-primary-muted,#e8f0ff)
with var(--app-primary-light). The hardcoded #e8f0ff fallback is a bright
light-blue that renders on dark backgrounds when the variable is undefined.
WizardTrainingStep: --font-sans → --font-body (correct token name).
ResumeSyncConfirmModal, ResumeLibraryCard, ResumeOptimizerPanel,
resume-review sub-pages: --font-sm → --text-sm across all occurrences.
--font-sm was never defined; most had a 0.875rem fallback (which matches
--text-sm) but the correct token should be referenced directly.
Replace git.circuitforge.io/circuitforge/peregrine with the correct
Forgejo URL (git.opensourcesolarpunk.com/Circuit-Forge/peregrine) in
Quick Start, License, and Contributing clone instructions.
Update feature matrix in docs/index.md to show BYOK‡ for AI features
(cover letter, company research, interview prep, survey assistant) with
a footnote explaining the free-with-own-backend model. Previously the
table showed these as Paid-only, which would mislead self-hosters.
Backend: add apt-get install libsqlcipher-dev before pip install so
pysqlcipher3 builds in the runner image.
Frontend: prep.test.ts was missing a qa mock (fetchFor now calls 5
endpoints in parallel; tests only mocked 4 — 5th returned undefined,
threw in catch, research.value never set). survey.test.ts: analyze()
was refactored from sync-result to async-task+poll; update test to
mock POST then poll completion.
Also remove Classic UI (Streamlit) button from AppNav — Streamlit is
deprecated and the button caused an unrecoverable redirect loop.
- Rename user-facing env var CF_ORCH_URL → GPU_SERVER_URL with full
backward-compat alias (closes#116). Priority chain: GPU_SERVER_URL
→ CF_ORCH_URL → orch.circuitforge.tech when CF_LICENSE_KEY present.
Write-back to os.environ[CF_ORCH_URL] keeps all downstream callers
unchanged.
- Add four task-routed llm.yaml backends (cf_cover_letter, cf_ats_rewrite,
cf_job_research, cf_interview_prep) using cf_orch.product + cf_orch.task.
Coordinator resolves model/node from assignments.yaml (closes#115).
- Update compose.yml, compose.cloud.yml, compose.test-cfcore.yml,
.env.example to use GPU_SERVER_URL as primary documented var.
index.html set 'html, body { background: #eaeff8 }' hardcoded.
body paints on top of html — even with html correctly going dark
via CSS variable resolution, the hardcoded body background covered it.
Fix:
- Remove body background from inline style (body is now transparent)
- Add blocking script to read cf-theme/cf-hacker-mode from localStorage
and set data-theme on <html> before first paint (FOUT prevention)
- Add html[data-theme='dark'|'solarized-dark'|'hacker'] rules so the
correct background fires immediately on initial load for all themes
peregrine.css used :root:not([data-theme="hacker"]) in the
prefers-color-scheme:dark block, causing --app-primary-light and
--app-accent-light to resolve to dark navy/brown in every explicit
light theme (light, solarized-light, colorblind) on dark-OS machines.
Changed to :root:not([data-theme]) to match theme.css's pattern,
so the media query only fires in auto mode. Explicit [data-theme="dark"]
block handles the dark-theme-on-light-OS case unchanged.
Also fixed incorrect fallback values in HintChip.vue (#0d1829 → #eaeff8)
and App.vue global toast (#2a3650/#eaeff8 → light-mode values).
Closes: dark elements in light themes on dark-OS machines