Purpose
This file is the Plane 1 knowledge anchor for the job search workflow (ARCH-0008, ARCH-0010). As a Plane 1 skill it lives under .claude/skills/job-scraper/ and provides the AI assistant with structured knowledge — it does not execute binaries or scrape via compiled code. The skill queries whatever job portals the user has configured, deduplicates results, performs a lightweight fit assessment, and presents new matches sorted by relevance. It covers REQ-1001 through REQ-1012.
The search core is country-agnostic (DEC-012, ADR-0004, NFR-0007): web search is the universal mechanism. No portal, country, locale, or site: string is hardcoded here — every target comes from the companion search-queries.md config. Pasted postings are a first-class input, never a fallback (DEC-011). The skill never fabricates postings (REQ-1012, ARCH-0007).
Trigger Phrases
- "Search for jobs" / "Find new jobs" / "Any new postings?"
- "Scrape job boards" / "Run a job search"
/search (optionally with a focus area, e.g. /search data science)
/scrape
- "Search broadly" / "Run all my queries"
- Any phrase about discovering new job postings
Companion Files
| File |
When to Read |
search-queries.md (this skill's folder) |
Always — the query strategy, portals, geography, and location-filter tiers come from here (data-req §17) |
job_scraper/seen_jobs.json |
Always — the deduplication registry (data-req §10); create {"seen": {}} if missing |
job_search_tracker.csv (repo root) |
Always — extract already-applied company+role pairs for dedup (REQ-1002) |
01-candidate-profile.md (job-application-assistant skill) |
When forming the quick-fit signal — to know the user's core skills |
This skill never modifies search-queries.md; it only reads it.
Contract — Steps by Requirement
Invocation (REQ-1001)
- Default (
/search, no argument): run the top 3 priority categories (Priority 1–3) from search-queries.md.
- Focus argument (e.g.
/search data science): prioritize the query categories that match the focus term, then fill with the highest-priority remaining categories.
- "broad" (
/search broad): run all query categories (Priority 1–4).
- Always load state (below) before issuing any search.
- No country-specific or portal-specific identifier is hardcoded in this skill; the breadth selection operates over whatever categories the user's config defines.
State Loading (REQ-1002)
Before searching, load all three sources:
job_scraper/seen_jobs.json — the seen registry. If the file is missing, create it with exactly {"seen": {}} (data-req §10).
job_search_tracker.csv (repo root) — extract the set of already-applied company + role pairs.
search-queries.md — the query strategy: Search Sites, Date Filter Rule, Priority 1–4 query groups, and Location Filter Tiers.
If search-queries.md still contains [UPPER_SNAKE_CASE] placeholder tokens, the profile has not been set up — tell the user to run /setup --section search rather than searching against template text.
Multi-Portal Web Search (REQ-1003)
- Build
WebSearch queries by combining the user's configured portals (Search Sites) × query strings (selected priority groups) × geography (from the queries / location tiers).
- Constrain to postings from the last 14 days (per the Date Filter Rule).
- Issue multiple searches in parallel where useful for efficiency.
- All
site: targeting, portal names, and country/region terms come verbatim from the user's config — never write a literal portal name or site: string into this skill. If the user configured site:<portal> entries, use them as-is; if they listed bare portal names, fold them into the query text.
Deterministic Listing Tier (REQ-1013, NFR-0021)
If a configured portal has an adapter (ADR-0004) exposing a deterministic list() pass,
prefer it: it returns structured postings (title, company, location, URL, date)
without spending tokens, pre-filtering candidates before any LLM work. Feed its
results into the same dedup + quick-fit steps below. Adapters are optional — when none
exists, web search (above) is the universal fallback (ARCH-0005). Prefer the cheap
deterministic pass over LLM calls for the listing stage wherever available (cost-aware
search, NFR-0021).
Fetch & Parse (REQ-1004)
Pre-filter before fetching (token efficiency): inspect search-result titles and snippets, discard obvious non-matches, and only WebFetch the promising ones.
For each fetched posting, extract: { title, company, location, posting date (or "recent"), url, key requirements (brief), application deadline (if listed) }.
On a fetch failure (gated portal, auth wall, dead link): do not error and do not abort. Prompt exactly:
"I couldn't fetch that posting. Paste it here and I'll use that."
Pasted postings are processed identically to fetched ones (DEC-011) — paste is first-class input.
Quick Fit Assessment (REQ-1005)
Assign each new job a lightweight three-level signal used only for sorting:
- High — role directly involves the user's core skills.
- Medium — role is adjacent to the user's experience.
- Low — role requires significant skills the user lacks.
This is explicitly NOT the full 5-dimension evaluation and NOT a numeric score — it is a sorting signal only. The full evaluation happens later in /apply.
Quick Legitimacy Flag (REQ-8003)
Alongside the fit signal, run a cheap legitimacy scan of each posting's title and
snippet against the high-severity signals in trust-safety/scam-patterns.json (upfront
fees, personal-data/ID/banking harvesting, off-platform redirects, money-movement, offer
without process). If a strong signal appears, mark the job ⚠ Suspicious in the
results so the user sees it before investing in /apply. This is a heuristic flag
only — the full legitimacy gate runs in /apply (REQ-8001). Never auto-skip a flagged
job (ARCH-0006); never invent a flag without the matching text (ARCH-0007).
Deduplication & State Update (REQ-1006, data-req §10, business-rules §6)
Skip a job if its URL is already a key in seen_jobs.json, or if its company+role pair already appears in job_search_tracker.csv.
Record every fetched job — new AND skipped — in seen_jobs.json under the key <url_or_company_title_key> (the posting URL, or a company+title combination when no stable URL exists). Each entry has exactly these fields:
{ "seen": { "<url_or_company_title_key>": { "title": "...", "company": "...", "url": "...", "first_seen": "YYYY-MM-DD", "fit": "high|medium|low", "status": "new|skipped|evaluated" } } }
status is new for a freshly surfaced match, skipped for a deduplicated/filtered-out job, evaluated once it has been handed to /apply.
first_seen is the ISO-8601 date the job was first recorded; do not overwrite it on later runs.
The registry grows monotonically — entries are never removed. Only jobs not already in the registry or tracker are presented to the user.
Geographic + Date Filtering (REQ-1010, REQ-1011)
Apply the Location Filter Tiers from search-queries.md:
- Ideal / Acceptable — include normally.
- Borderline — include but flag with a commute/relocation note; do not auto-skip.
- Too far — skip (unless the posting is remote).
Date rules:
- Skip jobs with expired deadlines and closed postings.
- Include jobs with no determinable date, but flag them as "date unknown".
Presentation (REQ-1007)
Sort by fit (high → medium → low). Present:
- Summary header:
Found X new positions (Y high, Z medium, W low match).
- A table with columns:
#, Fit, Title, Company, Location, Deadline, URL.
- For each high-match job: 2–3 bullets on why it matches, the key requirements to check, and any red flags.
- Then ask exactly:
"Want me to evaluate any of these in detail? Just give me the number(s)."
Detailed Evaluation Handoff (REQ-1008)
- When the user picks a number (or numbers), pass that job's URL or pasted posting text to
/apply for the full 5-dimension fit evaluation.
- Mark the handed-off job's
status as evaluated in seen_jobs.json — the dedup registry must stay in sync with downstream handoffs.
- The application tracker write stays in
/apply (REQ-1009) — this skill never writes to job_search_tracker.csv.
Provider Resilience (NFR-0022)
If the AI provider rate-limits, errors, or becomes unavailable mid-search, stop
gracefully with a clear message and whatever partial results exist — never retry-spam
or spin. Keep parallelism modest; do not run high-volume headless batches that can trip
provider abuse heuristics (ARCH-0005).
No Fabrication (REQ-1012, ARCH-0007)
- Present only jobs that came from real web-search results and have real URLs (or real pasted text the user supplied).
- Never invent a posting, company, or URL.
- If a search returns nothing, say so honestly — e.g. "No new postings matched your queries in the last 14 days." — rather than padding the list.
1---2name: job-scraper3description: Searches configured job portals via web search, deduplicates against seen jobs and the tracker, quick-rates fit, and presents new matches for evaluation. Country-agnostic — all portal/geography targeting comes from the user's config. Activates on: search for jobs, find new postings, scrape job boards, /search, /scrape.4---56## Purpose78This file is the Plane 1 knowledge anchor for the job search workflow (ARCH-0008, ARCH-0010). As a Plane 1 skill it lives under `.claude/skills/job-scraper/` and provides the AI assistant with structured knowledge — it does not execute binaries or scrape via compiled code. The skill queries whatever job portals the user has configured, deduplicates results, performs a lightweight fit assessment, and presents new matches sorted by relevance. It covers REQ-1001 through REQ-1012.910The search core is **country-agnostic** (DEC-012, ADR-0004, NFR-0007): web search is the universal mechanism. No portal, country, locale, or `site:` string is hardcoded here — every target comes from the companion `search-queries.md` config. Pasted postings are a first-class input, never a fallback (DEC-011). The skill never fabricates postings (REQ-1012, ARCH-0007).1112## Trigger Phrases1314- "Search for jobs" / "Find new jobs" / "Any new postings?"15- "Scrape job boards" / "Run a job search"16- `/search` (optionally with a focus area, e.g. `/search data science`)17- `/scrape`18- "Search broadly" / "Run all my queries"19- Any phrase about discovering new job postings2021## Companion Files2223| File | When to Read |24|------|--------------|25| `search-queries.md` (this skill's folder) | Always — the query strategy, portals, geography, and location-filter tiers come from here (data-req §17) |26| `job_scraper/seen_jobs.json` | Always — the deduplication registry (data-req §10); create `{"seen": {}}` if missing |27| `job_search_tracker.csv` (repo root) | Always — extract already-applied company+role pairs for dedup (REQ-1002) |28| `01-candidate-profile.md` (job-application-assistant skill) | When forming the quick-fit signal — to know the user's core skills |2930This skill never modifies `search-queries.md`; it only reads it.3132---3334## Contract — Steps by Requirement3536### Invocation (REQ-1001)3738- **Default** (`/search`, no argument): run the **top 3 priority categories** (Priority 1–3) from `search-queries.md`.39- **Focus argument** (e.g. `/search data science`): prioritize the query categories that match the focus term, then fill with the highest-priority remaining categories.40- **"broad"** (`/search broad`): run **all** query categories (Priority 1–4).41- Always load state (below) before issuing any search.42- No country-specific or portal-specific identifier is hardcoded in this skill; the breadth selection operates over whatever categories the user's config defines.4344### State Loading (REQ-1002)4546Before searching, load all three sources:47481. `job_scraper/seen_jobs.json` — the seen registry. If the file is missing, create it with exactly `{"seen": {}}` (data-req §10).492. `job_search_tracker.csv` (repo root) — extract the set of already-applied **company + role** pairs.503. `search-queries.md` — the query strategy: Search Sites, Date Filter Rule, Priority 1–4 query groups, and Location Filter Tiers.5152If `search-queries.md` still contains `[UPPER_SNAKE_CASE]` placeholder tokens, the profile has not been set up — tell the user to run `/setup --section search` rather than searching against template text.5354### Multi-Portal Web Search (REQ-1003)5556- Build `WebSearch` queries by combining the user's configured **portals** (Search Sites) × **query strings** (selected priority groups) × **geography** (from the queries / location tiers).57- Constrain to postings from the **last 14 days** (per the Date Filter Rule).58- Issue multiple searches in **parallel** where useful for efficiency.59- All `site:` targeting, portal names, and country/region terms come **verbatim from the user's config** — never write a literal portal name or `site:` string into this skill. If the user configured `site:<portal>` entries, use them as-is; if they listed bare portal names, fold them into the query text.6061### Deterministic Listing Tier (REQ-1013, NFR-0021)6263If a configured portal has an adapter (ADR-0004) exposing a deterministic `list()` pass,64**prefer it**: it returns structured postings (title, company, location, URL, date)65**without spending tokens**, pre-filtering candidates before any LLM work. Feed its66results into the same dedup + quick-fit steps below. Adapters are optional — when none67exists, web search (above) is the universal fallback (ARCH-0005). Prefer the cheap68deterministic pass over LLM calls for the listing stage wherever available (cost-aware69search, NFR-0021).7071### Fetch & Parse (REQ-1004)7273- **Pre-filter before fetching** (token efficiency): inspect search-result titles and snippets, discard obvious non-matches, and only `WebFetch` the promising ones.74- For each fetched posting, extract: **{ title, company, location, posting date (or "recent"), url, key requirements (brief), application deadline (if listed) }**.75- **On a fetch failure** (gated portal, auth wall, dead link): do not error and do not abort. Prompt exactly:76 > "I couldn't fetch that posting. Paste it here and I'll use that."77 78 Pasted postings are processed **identically** to fetched ones (DEC-011) — paste is first-class input.7980### Quick Fit Assessment (REQ-1005)8182Assign each new job a lightweight **three-level** signal used only for sorting:8384- **High** — role directly involves the user's core skills.85- **Medium** — role is adjacent to the user's experience.86- **Low** — role requires significant skills the user lacks.8788This is explicitly **NOT** the full 5-dimension evaluation and **NOT** a numeric score — it is a sorting signal only. The full evaluation happens later in `/apply`.8990### Quick Legitimacy Flag (REQ-8003)9192Alongside the fit signal, run a **cheap** legitimacy scan of each posting's title and93snippet against the high-severity signals in `trust-safety/scam-patterns.json` (upfront94fees, personal-data/ID/banking harvesting, off-platform redirects, money-movement, offer95without process). If a strong signal appears, mark the job **⚠ Suspicious** in the96results so the user sees it **before** investing in `/apply`. This is a heuristic flag97only — the full legitimacy gate runs in `/apply` (REQ-8001). Never auto-skip a flagged98job (ARCH-0006); never invent a flag without the matching text (ARCH-0007).99100### Deduplication & State Update (REQ-1006, data-req §10, business-rules §6)101102- **Skip** a job if its URL is already a key in `seen_jobs.json`, **or** if its company+role pair already appears in `job_search_tracker.csv`.103- **Record every fetched job — new AND skipped** — in `seen_jobs.json` under the key `<url_or_company_title_key>` (the posting URL, or a company+title combination when no stable URL exists). Each entry has exactly these fields:104105 ```json106 { "seen": { "<url_or_company_title_key>": { "title": "...", "company": "...", "url": "...", "first_seen": "YYYY-MM-DD", "fit": "high|medium|low", "status": "new|skipped|evaluated" } } }107 ```108109 - `status` is `new` for a freshly surfaced match, `skipped` for a deduplicated/filtered-out job, `evaluated` once it has been handed to `/apply`.110 - `first_seen` is the ISO-8601 date the job was first recorded; do not overwrite it on later runs.111- The registry **grows monotonically** — entries are never removed. Only jobs not already in the registry or tracker are presented to the user.112113### Geographic + Date Filtering (REQ-1010, REQ-1011)114115Apply the **Location Filter Tiers** from `search-queries.md`:116117- **Ideal / Acceptable** — include normally.118- **Borderline** — include but **flag** with a commute/relocation note; do **not** auto-skip.119- **Too far** — **skip** (unless the posting is remote).120121Date rules:122123- Skip jobs with **expired deadlines** and **closed postings**.124- Include jobs with **no determinable date**, but **flag** them as "date unknown".125126### Presentation (REQ-1007)127128Sort by fit (high → medium → low). Present:1291301. Summary header: **`Found X new positions (Y high, Z medium, W low match)`**.1312. A table with columns: **`#, Fit, Title, Company, Location, Deadline, URL`**.1323. For **each high-match job**: 2–3 bullets on why it matches, the key requirements to check, and any red flags.1334. Then ask exactly:134 > "Want me to evaluate any of these in detail? Just give me the number(s)."135136### Detailed Evaluation Handoff (REQ-1008)137138- When the user picks a number (or numbers), pass that job's **URL or pasted posting text** to **`/apply`** for the full 5-dimension fit evaluation.139- Mark the handed-off job's `status` as `evaluated` in `seen_jobs.json` — the dedup registry must stay in sync with downstream handoffs.140- The **application tracker write stays in `/apply`** (REQ-1009) — this skill never writes to `job_search_tracker.csv`.141142### Provider Resilience (NFR-0022)143144If the AI provider rate-limits, errors, or becomes unavailable mid-search, stop145**gracefully** with a clear message and whatever partial results exist — never retry-spam146or spin. Keep parallelism modest; do not run high-volume headless batches that can trip147provider abuse heuristics (ARCH-0005).148149### No Fabrication (REQ-1012, ARCH-0007)150151- Present **only** jobs that came from real web-search results and have **real URLs** (or real pasted text the user supplied).152- Never invent a posting, company, or URL.153- If a search returns nothing, say so honestly — e.g. "No new postings matched your queries in the last 14 days." — rather than padding the list.