You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3.6 KiB

gimme-job Learn Mode — System Instructions

You are working inside the gimme-job codebase.

Your Goal

Create or update a job site adapter so that gimme-job run --site {site_id} works without any AI assistance at runtime.

Core Rules

  1. Runtime must not require AI. The adapter must work with pure Python + Playwright.
  2. Use Chrome persistent profile JobAgent — never browser.new_context() directly.
  3. No storage_state — Chrome profile reuse is the only session persistence mechanism.
  4. Prefer robust selectors: aria-label, data-* attributes, semantic HTML. Avoid brittle nth-child.
  5. Always define fallback selectors (list multiple per field in the manifest).
  6. Handle zero-result states explicitlyZERO_RESULTS_EXPECTED is a normal exit.
  7. No detail page visits by default — extract from card list only.
  8. If a date filter exists in the UI, use it.

Files to Generate

For site_id {site_id}:

  1. sites/{site_id}.yaml — Site manifest YAML
  2. gimme_job/adapters/{site_id}.py — Adapter Python file
  3. tests/adapters/test_{site_id}.py — Smoke test
  4. workspace/manifests/{site_id}.learning-report.md — Learning report

Adapter Interface

The adapter must implement BaseJobSiteAdapter from gimme_job/adapters/base.py:

class BaseJobSiteAdapter(Protocol):
    site_id: str
    def prepare(self, page: Page, config: SiteManifest) -> None: ...
    def apply_search(self, page: Page, query: SearchQuery) -> None: ...
    def apply_filters(self, page: Page, query: SearchQuery) -> None: ...
    def collect_cards(self, page: Page, config: SiteManifest) -> list[RawJobCard]: ...
    def paginate(self, page: Page, page_index: int, config: SiteManifest) -> bool: ...
    def normalize(self, raw: RawJobCard) -> JobPostingCandidate: ...

Use @register("{site_id}") from gimme_job/adapters/registry.py to register the adapter.

Manifest Schema

Key YAML fields (see gimme_job/models/manifest.py for full Pydantic schema):

site_id: {site_id}
enabled: true
repair_needed: false
identity:
  label: "Site Name"
  base_url: "https://..."
  start_url_template: "https://...?q={keywords_urlencoded}"
browser:
  profile_mode: persistent_chrome_profile
  profile_name: JobAgent
  headed_on_learn: true
  headless_on_run: true
search:
  keyword_mode: url_param   # url_param | input_field | none
  date_mode: none           # none | url_param | click_filter
  date_filter_text: null
  result_list_wait_selector: null
pagination:
  mode: none                # none | next_button | url_increment
  next_button_selectors: []
  max_pages: 3
extract:
  container_selectors:
    - "CSS selector for each job card container"
  fields:
    title:
      text: ["h2 a", "h3"]
    company:
      text: [".company-name"]
    location:
      text: [".location"]
    posted_text:
      text: ["time", ".date"]
    url:
      attr:
        selector: "a[href*='/jobs/']"
        name: href
post_filters:
  include_posted_text:
    - "today"
    - "1 day ago"

Smoke Test Requirements

The smoke test in tests/adapters/test_{site_id}.py must:

  1. Open the job search page
  2. Either detect result cards OR confirm zero-result state
  3. Extract at least 2 fields from a card (title + one other) OR confirm zero results
  4. Pass with pytest tests/adapters/test_{site_id}.py

Acceptance Criteria

Learning is successful when ALL of these are true:

  • sites/{site_id}.yaml exists and is valid
  • gimme_job/adapters/{site_id}.py exists with no syntax errors
  • tests/adapters/test_{site_id}.py exists
  • gimme-job test {site_id} passes (smoke test)
  • At least 1 field extraction logic is verified