Thanks for your interest in contributing to JobSearch RAG. This document covers the development setup, coding standards, testing philosophy, and PR process.
# Clone
git clone https://github.com/grimlor/jobsearch-rag.git
cd jobsearch-rag
# Install with dev dependencies (creates .venv automatically)
uv sync --extra dev
# Optional: auto-activate venv
direnv allow
# Install Playwright browser
uv run playwright install chromiumAll checks must pass before submitting a PR:
task check # runs lint → type → testOr individually:
task lint # ruff check src/ tests/
task format # ruff format src/ tests/
task type # pyright type checking
task test # pytest -v- Python 3.11–3.13 — use modern syntax (
X | Yunions,matchstatements where appropriate). CI tests all three versions on Ubuntu, macOS, and Windows. from __future__ import annotationsat the top of every module.- ruff handles formatting and import sorting. Don't fight it.
- pyright — all functions need type annotations. No
Anyunless you have a good reason and document it. - Line length: 99 characters (configured in
pyproject.toml).
Tests are the living specification. Every test class documents a behavioral requirement, not a code structure.
class TestYourFeature:
"""
REQUIREMENT: One-sentence summary of the behavioral contract.
WHO: Who depends on this behavior (operator, pipeline runner, etc.)
WHAT: What the behavior is, including failure modes
WHY: What breaks if this contract is violated
"""
def test_descriptive_name_of_scenario(self) -> None:
"""What this scenario proves in plain English."""
...-
Mock I/O boundaries, not implementation. Use HTML fixtures and data fixtures. Don't mock internal method calls — that couples tests to implementation details and makes refactoring painful.
-
Failure specs matter. For every happy path, ask: what goes wrong? Write specs for those failure modes. An unspecified failure is an unhandled failure.
-
Missing spec = missing requirement. If you find a bug, the first step is always adding the test that should have caught it, then fixing the code to pass that test.
-
Docstrings on every test method. One sentence explaining what the test proves. This makes test output readable as a specification.
-
Diagnostic assertion messages. Every
assertmust include a message explaining what went wrong:# ✅ Good assert result is not None, "Expected non-None result from query" assert score > 0.5, f"Expected score > 0.5 for qualified role, got {score}" # ❌ Bad assert result is not None assert score > 0.5
HTML fixtures live in tests/fixtures/. When adding a new board adapter:
- Capture real page HTML from the board (sanitize any personal data)
- Create both search results and detail page fixtures
- Validate fixture structure matches what the live site actually serves
See ARCHITECTURE.md for system design, the adapter pattern, and how the pieces connect.
- Create
src/jobsearch_rag/adapters/yourboard.py - Subclass
JobBoardAdapterand implement all abstract methods - Decorate with
@AdapterRegistry.register - Add config in
settings.toml - Create fixtures and write tests organized by behavioral requirement
The adapter must produce JobListing instances. Everything downstream
picks it up automatically via the registry.
Follow Conventional Commits.
feat(scraper): add WeWorkRemotely adapter with fixture-based tests
- Implement search pagination and JD extraction
- Add HTML fixtures for search results and detail page
- 24 tests covering extraction, auth failures, and edge cases
This project uses universal-dev-skills for AI coding agent configuration (skills, instructions, and agents). Clone that repo and follow its README to configure your editor. No per-repo setup is required — the skills apply automatically across all workspaces.
- Branch from
main. - All checks must pass —
task check(lint + type + test). - Include tests for any new behavior or bug fix.
- One concern per PR — don't mix a new feature with unrelated refactoring.
- Describe what and why in the PR description. If it changes adapter behavior, note whether existing tests needed modification (ideally not).
Changes to the following paths have security implications and require explicit justification in the PR description:
src/jobsearch_rag/adapters/— adapter code runs authenticated Playwright sessions; changes here can affect session cookie handling and browser behaviorsrc/jobsearch_rag/rag/scorer.py— constructs the disqualifier LLM prompt; changes here affect prompt injection attack surfacesrc/jobsearch_rag/adapters/base.py—JobListingdataclass is the trust boundary; changes to validation affect all downstream processingsrc/jobsearch_rag/output/jd_files.py— constructs file paths from user-influenced data; changes here affect path traversal risk
CI flags PRs touching these paths with a security warning. When your PR modifies security-sensitive paths, include a brief note in the description explaining why the change is safe. See SECURITY.md for the full threat model.
When filing an issue:
- Bug: Include the error message, what you expected, and steps to reproduce. If it's an adapter issue, note which board and whether it works against the fixture HTML.
- Feature request: Describe the problem you're trying to solve, not just the solution you have in mind.
- New board adapter: Open an issue first to discuss the board's structure before starting implementation. Some boards (e.g., LinkedIn) have detection mechanisms that require careful design.