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.

110 lines
3.6 KiB
Markdown

# 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 explicitly**`ZERO_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`:
```python
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):
```yaml
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