feat(claude): add job‑application‑assistant skill and related commands/files

This commit is contained in:
Prad Nukala committed 2026-08-26 10:33:55 -04:00
1 parent f091cc110a
commit a46f153f22
27 files changed
+4862

No files matched your search

+269
View File
@@ -0,0 +1,269 @@
---
name: scrape
description: >
Finds new job postings matching your profile via installed portal-search CLIs
(LinkedIn, local job boards, and any skills added with /add-portal). Deduplicates
across runs. Triggers on: job scrape, find jobs, search jobs, new jobs, job search,
scrape jobs, /scrape
allowed-tools: Read, Write, Edit, Glob, Grep, Bash(bun --version), Bash(bun run .agents/skills/*/cli/src/cli.ts *), WebFetch, WebSearch, Agent, AskUserQuestion
---
# Job Scraper
---
## How It Works
This skill searches job portals using the **installed portal-search CLIs** in
`.agents/skills/` (plus WebSearch as a fallback), using queries from your profile.
It deduplicates against previously seen jobs and the application tracker, and
presents new matches with a quick fit assessment.
## Invocation
The user triggers this skill by saying things like:
- "Find new jobs"
- "Scrape for jobs"
- "Any new positions?"
- "/scrape"
Optional arguments:
- A focus area, e.g. "/scrape data science" or "/scrape geophysics"
- "broad" to run all search categories, e.g. "/scrape broad"
- "health" to run the portal health check only (Step 4.75), without searching, deduplicating, or presenting jobs - e.g. "/scrape health", or "/scrape health jobnet" to probe one portal even if disabled
---
## Execution Steps
### Step 0: Load State
1. Read `job_scraper/seen_jobs.json` (create if missing - start with `{"seen": {}}`)
2. Read `job_search_tracker.csv` to extract already-applied companies+roles
3. Read `search-queries.md` (this directory) for the search strategy
### Step 1: Search
Read `search-queries.md` (this directory) for the search strategy. By default, run the top 3 priority query categories. If the user said "broad", run all categories. If the user specified a focus area (e.g. "data science"), prioritize queries from that category.
**Use the installed CLI tools as the primary search mechanism.** Fall back to `WebSearch` only for portals that do not have a CLI skill, or if `bun` is unavailable on the system.
#### 1a. Check bun availability
```bash
bun --version
```
If this fails (bun not installed), skip to **1c (WebSearch fallback)** for all portals and note the fallback in the Step 5 output.
#### 1b. Run CLI tools (primary — run these in parallel where possible)
Discover all installed portal CLI skills by reading every `SKILL.md` found under `.agents/skills/*/SKILL.md`. Each file documents that portal's exact CLI flags and usage examples. **Use each portal's own documented interface — do not guess flags.** This approach automatically includes any new portals added via `/add-portal` without requiring changes to this file.
**Honor the `enabled` toggle.** A portal is enabled unless its `SKILL.md` frontmatter sets `enabled: false` (a missing key means enabled — the default). Skip each disabled portal and record it for the Step 5 summary. A fork can thus keep a portal installed but sit out a run without deleting its directory.
For each **enabled** portal skill:
1. Read its `SKILL.md` to find the correct `bun run …` invocation and supported flags.
2. Translate the query terms from `search-queries.md` into that portal's flag format (e.g. `--key`, `--search-string`, `--query`, filter codes — whatever the portal's SKILL.md specifies).
3. Scope to the last 14 days using the portal's supported recency **filter** flag (`--jobage`, `--since <YYYY-MM-DD>`, etc. — as documented per portal). A portal with **no recency flag** (jobdanmark offers none) still gets scoped: every portal's search output carries a `date` field, so filter client-side — drop results whose `date` is older than 14 days after the call returns, and never invent a flag the portal's SKILL.md does not document (the CLIs reject unknown flags). `--order PublicationDate` is a sort, and a sort is not a filter — pairing it with a `--limit` is a defensible approximation on a portal that offers nothing better (jobnet), but apply the client-side date filter on top all the same.
4. Cap results to ~20 per call using the portal's limit flag.
5. Use `--format json` for machine-readable output.
Run all portal CLI calls in parallel where possible using the Agent tool. Collect all `results` arrays into a single pool for Step 2, keeping each result tagged with its source portal skill (for Step 2 `detail` lookups).
If a CLI tool exits with a non-zero code, log the error message and continue — do not abort the whole search.
#### 1c. WebSearch fallback
Use `WebSearch` for:
- Portals listed in `search-queries.md` that do **not** have a corresponding directory under `.agents/skills/`
- Any portal whose CLI fails at runtime
- When bun is unavailable (Step 1a failed)
Use the site-specific query strings from `search-queries.md` directly as WebSearch queries for these portals.
Tag each fallback result as WebSearch-sourced, keeping the portal tag when the fallback stands in for an installed portal whose CLI failed. Step 4 persists this as the entry's `source`, and Step 5 reports which portals ran on the fallback this run.
### Step 2: Fetch & Parse
For each promising result from Step 1:
**From CLI results:** Search output already includes title, company, location, date,
and URL. For jobs worth a deeper look, fetch full detail with that portal's `detail`
command (see its SKILL.md — do not guess flags) to extract **key requirements**,
**application deadline**, and a brief description snippet.
**From WebSearch results:** Use `WebFetch` on the posting URL and extract the same
fields manually. If it returns HTTP 403, retry with browser headers via curl per
`.claude/skills/job-application-assistant/09-web-research.md` before giving up — most
bank and corporate sites reject WebFetch's user agent while serving browsers normally.
**Store a URL that actually resolves to the posting.** A listing-page URL with a
`#fragment` appended (`.../jobs/ciso/#ikerian`) is not a posting: it fetches fine and
returns unrelated job titles, which makes every later `/rank` and `/apply` run fail on
that entry. When WebSearch only yields a listing page, search the employer's own careers
site for the role and store that URL instead, or drop the candidate rather than saving a
fragment link.
For every candidate:
- Skip if the URL or company+title combo already exists in `seen_jobs.json`
- Skip if the company+role already appears in `job_search_tracker.csv`
### Step 2.5: Mass-Posting Detection (within this run)
A distribution pattern worth flagging to the user as a caution signal, not as an accusation against the employer - it describes how a listing is being distributed, not a verdict on whether the company is legitimate. It alone proves nothing is wrong (companies do legitimately hire the same role across several cities); flag it so the user can factor it in when deciding whether to invest time, don't downgrade fit or silently exclude the result because of it.
If two or more results in this run's pool (from the same company, or sharing the same req/job ID visible in the URL or title) have substantially the same description and differ only in city/location/title, don't present them as separate rows. Consolidate into a single row and note the spread, e.g. "posted identically across 6 cities (BR, MX, GT)".
### Step 3: Quick Fit Assessment
For each new job, do a rapid fit check (NOT the full evaluation from `04-job-evaluation.md` - just a quick signal):
- **High match**: Role directly involves your core skills
- **Medium match**: Role is adjacent to your experience
- **Low match**: Role requires significant skills you lack
**Language override:** before assigning a match level, check the posting against `04-job-evaluation.md`'s Language Gate (a required language you haven't declared at all in your CLAUDE.md Languages table). A required language that's entirely undeclared overrides skill fit: mark it **Low** regardless of how well the skills align, and name it in the highlight bullets so it isn't buried under an otherwise-good-looking match. A **declared** language at a requirement that reads higher than your declared level is *not* an override — score fit normally, but add a red-flag bullet under that job's highlights (Step 5) quoting the posting's requirement next to your declared level, so the gap is visible without being auto-downgraded.
### Step 4: Deduplicate & Store
1. Add ALL fetched jobs (new and skipped) to `seen_jobs.json` with structure:
```json
{
"seen": {
"<url_or_company_title_key>": {
"title": "...",
"company": "...",
"url": "...",
"first_seen": "YYYY-MM-DD",
"deadline": "YYYY-MM-DD" | null,
"fit": "high/medium/low",
"status": "new/skipped/ranked/expired",
"portal": "<source portal skill, e.g. jobindex-search>",
"source": "cli/websearch"
}
}
}
```
The `portal` field records which CLI skill produced the job (results are already tagged per portal in Step 1b - persist that tag here). Entries written before this field existed lack it; the health check (Step 4.75) attributes those by matching the URL's domain against each portal's base URL, so do not backfill.
The `source` field records which mechanism produced the entry: `cli` for Step 1b portal-CLI output, `websearch` for the Step 1c fallback. This is what keeps a ghost-job report diagnosable after the run's summary is gone: a stored entry whose URL later resolves to nothing (or to a different job) reads very differently depending on whether it came from live CLI output or from a search index that can be weeks stale - and a presented job with no entry here at all points at fabrication, which Rule 1 forbids. Entries written before this field existed lack it; never backfill it - the mechanism was not recorded.
`/rank` extends this schema additively: ranked entries also carry `rank_score` (0–100 overall score), `rank_verdict` (fit band, e.g. "strong fit"), `rank_date` (ISO date of ranking), the veto fields `location_verdict` and `language_gate` (both PASS/FAIL/FLAG) with `language_note` (the quoted requirement explaining a non-PASS), and `strengths`/`gaps` (1-3 verbatim bullets each, copied from the scoring agent's findings). The `status` field is set to `"ranked"`. Do not drop any of these fields when re-writing entries. Entries ranked before `strengths`/`gaps` existed simply lack them; readers tolerate their absence and never backfill by guessing. Entries ranked before the verdict rename may carry a legacy PASS/FAIL/FLAG string in `location` - read that as the verdict when `location_verdict` is absent; in fresh entries `location` is always a place, never a verdict.
`deadline` is a base field rather than a `/rank` extension: Step 2's detail fetch already extracts the application deadline, so it is written when the job is first seen and refreshed by `/rank` Step 4 when a scoring agent returns a different value. `null` means the posting states no deadline; a missing key means the entry predates this field - **never infer a deadline** from either, and never backfill by guessing.
2. Only present jobs NOT already in the seen list or tracker.
### Step 4.5: Generate Referral Contact Links (High & Medium Fit Only)
For every job from this run with `fit` of **high** or **medium** (skip low-fit jobs),
build two LinkedIn people-search URLs so the user can find a recruiter or team member to
reach out to for a referral or a warm intro. This is deliberately a link-generation step,
not an automated lookup: no scraping, no third-party API, zero runtime dependencies or
credentials required.
**A. Recruiters / Talent Acquisition (the referral path)**
```
https://www.linkedin.com/search/results/people/?keywords=<url-encoded "<Company Name> recruiter">&origin=GLOBAL_SEARCH_HEADER
```
**B. Role/team peers (informational-outreach / warm-intro path)**
```
https://www.linkedin.com/search/results/people/?keywords=<url-encoded "<Company Name> <role keyword>">&origin=GLOBAL_SEARCH_HEADER
```
Use a short keyword drawn from the posting's title for `<role keyword>` - e.g. a posting
titled "AI Program Manager" becomes `"<Company Name> AI Program Manager"`.
Both links are for the user to open and browse themselves - never fetch or scrape the
LinkedIn people-search result pages programmatically. Never fabricate contacts or claim a
specific person was found; these are search links, not results.
### Step 4.75: Portal Health Check
Scraper-based portal CLIs rot silently: when a portal changes its markup, the parser usually exits 0 with zero results or with null/garbled fields, and the Step 1c fallback never fires because it only triggers on hard failure. This step catches that from evidence the run already holds.
**Free pass (no extra requests).** For each enabled portal that ran in Step 1b:
- **Degraded scan:** inspect the results it returned this run. Flags: `company` null or empty on every result, empty titles, undecoded entities (`&amp;`) or HTML fragments in titles, URLs that do not point at the portal. Any of these means the parser is half-working and `/scrape` is silently collecting junk.
- **Yield history:** if the portal returned zero results across all of this run's queries, check whether `seen_jobs.json` holds prior entries from it (via the `portal` field, or by matching URL domains for entries predating the field). A portal that produced jobs on earlier runs and produces nothing now is suspect - the same queries worked before.
**Escalation (bounded, on suspicion only).** A suspect portal gets **one** sentinel probe: run its documented `search` with the example query from its own SKILL.md (that query provably worked when the skill was registered), the portal's limit flag capped at 3, `--format json`. If that returns nothing, retry **once** with a single common word. Only then is the verdict **broken**. A 429 or block page is **never** evidence of breakage - record the portal as **inconclusive (rate-limited)**, back off, and do not retry.
**Verdicts.** Healthy portals get silence - no table, no line. Anything else surfaces in the Step 5 summary as a health line.
**Probe-only mode (`/scrape health`).** Skip Steps 1-4 and this step's free pass (there is no fresh run to scan); instead probe every installed portal directly - enabled ones by default, a disabled one only when named explicitly (e.g. `/scrape health jobnet`). Each portal gets the sentinel probe above, the degraded criteria applied to whatever it returns, and - since the user explicitly asked for diagnosis - one `detail` fetch on the first result of each healthy portal (description must be readable decoded text; a failure downgrades to degraded). Report all statuses in this mode, including healthy. Volume stays bounded: one search, at most one retry, at most one detail per portal.
### Step 5: Present Results
Present new jobs in a table sorted by fit (high first). When Step 1b skipped
portals (`enabled: false`), report them with the `skipped (disabled):` line below
so opting one out stays visible rather than silent; omit the line when nothing
was skipped. When any portal's results came from the Step 1c fallback this run
(bun unavailable, or its CLI failed at runtime), report it with the
`fallback (websearch):` line - fallback results come from a search index that
can be stale, so the reader should know which rows carry that caveat; omit the
line when every portal ran its CLI. When Step 4.75 found a portal degraded, broken, or inconclusive,
add one `health:` line per suspect portal (healthy portals get no line); after
the report, offer to set that portal's `enabled: false` so `/scrape` stops
running it (and covers it via the Step 1c fallback) until it is fixed - only
edit the toggle with the user's confirmation, and never edit anything else in
the skill.
```
## New Job Matches - YYYY-MM-DD
Found X new positions (Y high, Z medium, W low match).
skipped (disabled): <portal-name>, <portal-name>
fallback (websearch): <portal-name>, <portal-name>
health: <portal-name> - degraded (company null on all 12 results); parsing anchors in .agents/skills/<portal-name>/url-reference.md
health: <portal-name> - broken (0 results for the SKILL.md test query and a broader retry); parsing anchors in .agents/skills/<portal-name>/url-reference.md
| # | Fit | Title | Company | Location | Deadline | URL |
|---|-----|-------|---------|----------|----------|-----|
| 1 | High | ... | ... | ... | ... | [Link](...) |
If Step 2.5 flagged a mass-posting pattern, note it in the Title cell (e.g. "Frontend Developer (posted in 6 cities)") rather than burying it. Do the same for a declared-language-insufficient-level flag from the Language Gate (e.g. "Backend Engineer ⚠ fluent English required") - both are signals the user should see at a glance, not just in the detail highlights below.
### High-Match Highlights
For each high-match job, add 2-3 bullet points:
- Why it matches your profile
- Key requirements to check
- Any red flags (including mass-posting signals from Step 2.5)
### Contacts
For each high/medium-fit job from Step 4.5, add a short contacts block with the two
LinkedIn search links:
- Recruiters/TA search link, for the referral path
- Role/team-peer search link, for the warm-intro / informational-outreach path
```
After presenting, ask:
> "Want me to evaluate any of these in detail? Just give me the number(s)."
If the user picks a number, invoke the **job-application-assistant** skill workflow (fit evaluation first, then CV + cover letter if approved).
If the run found many new jobs (roughly 8+), also suggest `/rank` - it batch-scores all new postings against the full fit framework and returns a ranked shortlist, which beats eyeballing a long table. (`/rank` sets the `ranked` and `expired` status values in `seen_jobs.json`; treat both as already-seen for dedup purposes.)
### Step 6: Update Tracker (Optional)
If the user decides to apply to any job, the tracker row is written by **job-application-assistant Step 3b**, which Step 5 already routes into - do not add a second row here. Only when the user says they applied to something outside that path, add a row using the header and the match-then-update rule in `/outcome` Step 1.
---
## Important Rules
1. **Never fabricate job postings.** Only present jobs from actual CLI search/detail output or WebSearch/WebFetch results.
2. **Respect deduplication.** Always check seen_jobs.json AND job_search_tracker.csv before presenting.
3. **Focus on configured geographic area.** Skip jobs that require relocation or are clearly outside commute range.
4. **Only open positions.** Skip postings with expired deadlines or those marked as closed.
5. **Be efficient with detail fetches.** Don't run `detail` or WebFetch on every search hit — pre-filter by title/snippet, then fetch only promising matches.
6. **Parallel searches.** Run portal CLI searches in parallel; use WebSearch only for gaps the CLIs don't cover.
7. **No automated people lookups.** Referral contacts (Step 4.5) are LinkedIn search links only - never fetch or scrape LinkedIn people-search result pages programmatically.
8. **Health checks are bounded and honest.** Step 4.75 spends at most one probe, one retry, and (in `health` mode) one detail fetch per portal - a diagnosis, not a crawl. A rate-limit is never evidence of breakage. Health verdicts come only from observed CLI output; a portal that could not be tested is reported as inconclusive, never guessed. The `enabled` toggle is the only thing the health check may edit, and only with confirmation.
9. **Flag distribution patterns, never accuse.** The mass-posting signal (Step 2.5) describes how a listing is being distributed, not a claim that the employer is a scam. Never name a company as fraudulent or untrustworthy - present the observation and let the user decide.
+128
View File
@@ -0,0 +1,128 @@
# Search Queries for Job Scraper
<!-- Generated by /setup on 2026-08-20 for Prad Nukala. Re-run `/setup --section search` to change. -->
## Installed portal CLIs (primary for `/scrape`)
`/scrape` discovers every portal skill under `.agents/skills/*/SKILL.md` and runs its CLI first. Enabled here:
- **`linkedin-search`** (live, public listings)
- **`freehire-search`** (live, ATS aggregator, tech-focused; use `--region us` / `--country US`)
- **`wellfound-search`** (live, Wellfound startup listings by role + city or remote; page 1 only, so vary role wording: `senior software engineer`, `backend engineer`, `founding engineer`, `blockchain engineer`; results carry compensation ranges, useful for the salary floor)
- **`jobsdb-search`** (local snapshot `documents/postings/jobs.db`, ~87k postings, crypto/fintech/AI-heavy, NYC/SF-dense). **Static snapshot**: recency is relative to `meta.snapshot`, not today, and every hit must be URL-verified live before it is shown. See its SKILL.md.
Danish demo portals (jobindex, jobbank, jobdanmark, jobnet) stay `enabled: false`.
The `site:` query templates in this file are the **WebSearch fallback** for portals without a CLI, company career pages, or when a CLI fails.
**Language scope:** all queries in English. The candidate's other declared language (Telugu) has no job-market relevance for these roles; do not generate Telugu queries. A posting that requires a language other than English as a job condition fails the Language Gate in `04-job-evaluation.md`.
## Search Sites
Primary:
- **linkedin.com/jobs** (filter: United States / New York / San Francisco / Remote); also covered by `linkedin-search` CLI
- **freehire.me**, covered by `freehire-search` CLI
- **documents/postings/jobs.db**, covered by `jobsdb-search` CLI
- **wellfound.com**, covered by `wellfound-search` CLI (`site:wellfound.com` as WebSearch fallback)
- **web3.career**, **cryptocurrencyjobs.co** (WebSearch `site:` fallback; scaffold a CLI with `/add-portal` if they prove productive)
Secondary (company career pages via Google): `site:` searches against the target companies below.
## Target Companies
Monitor directly (career pages, plus `jobsdb-search --company`): Circle, Ellipsis Labs, Mural Pay, Gemini, Coinbase, Privy, Turnkey, Fireblocks, Dynamic, Alchemy, Anchorage Digital, Polymarket, Block, Anthropic. The `jobsdb-search --tier 1` list is the broader target set.
## Query Categories
Queries are grouped by priority and **organized by function, not title**. By default `/scrape` runs Priorities 1-3. Combine each query with location terms (`New York`, `San Francisco`, `Remote`, `United States`) where the portal supports it.
Portal flag translation:
- `linkedin-search`: `--query "<title>" --location "New York, United States"` (and `"San Francisco Bay Area"`, `--remote remote`), `--jobage 14`
- `freehire-search`: `-q "<keyword>" --country US --seniority senior,staff --category backend` (or `--remote remote`), `--jobage 14`
- `wellfound-search`: `-q "<role title>" --location new-york` / `"San Francisco"` / `--remote`, `--jobage 14 --no-description`
- `jobsdb-search`: `-q "<keyword>" --seniority senior,staff_principal --location "New York"` / `"San Francisco"` / `--remote remote,hybrid`, `--jobage 30`; add `--crypto-native` or `--sector crypto,fintech-nc` for Priority 2
### Priority 1: Senior Backend & Protocol Engineering (Staff titles are a stretch, keep but deprioritize)
The strongest and most desired direction: owning backend systems, protocols, and infrastructure in Go/TypeScript at crypto-native, fintech, or AI-infrastructure companies.
Titles: Staff Software Engineer, Senior Backend Engineer, Senior Software Engineer (Go), Protocol Engineer, Blockchain Engineer, Staff Backend Engineer, Senior Platform Engineer, Senior Infrastructure Engineer
```
site:linkedin.com/jobs "staff software engineer" Go New York
site:linkedin.com/jobs "senior backend engineer" Go OR Golang New York OR "San Francisco" OR remote
site:linkedin.com/jobs "protocol engineer" United States
site:linkedin.com/jobs "blockchain engineer" Cosmos OR IBC OR EVM New York OR remote
site:web3.career "backend engineer" Go
site:cryptocurrencyjobs.co "protocol engineer"
```
### Priority 2: Founding Engineer at Early-Stage Startups
Zero-to-one roles where architecture, delivery, and reliability across the stack is the job. Equity-heavy, small team, seed to Series A.
Titles: Founding Engineer, Founding Backend Engineer, Founding Full-Stack Engineer, First Engineer, Technical Co-Founder, Head of Engineering (seed stage)
```
site:linkedin.com/jobs "founding engineer" New York OR "San Francisco" OR remote
site:wellfound.com "founding engineer" backend OR fullstack
site:linkedin.com/jobs "founding backend engineer"
site:linkedin.com/jobs "first engineer" OR "engineer #1" startup
site:ycombinator.com/companies/jobs "founding engineer"
```
### Priority 3: Applied Cryptography, Wallet & Identity Infrastructure
Domain-expertise roles: MPC/threshold signatures, passkeys/WebAuthn, key management, wallet-as-a-service, decentralized identity.
Titles: Cryptography Engineer, Applied Cryptographer, Security Engineer (Cryptography), Wallet Infrastructure Engineer, Key Management Engineer, Identity Engineer
```
site:linkedin.com/jobs "cryptography engineer" United States
site:linkedin.com/jobs "applied cryptography" MPC OR "threshold signatures"
site:linkedin.com/jobs WebAuthn OR passkeys engineer
site:linkedin.com/jobs "wallet infrastructure" engineer
site:cryptocurrencyjobs.co cryptography
```
### Priority 4: Adjacent Roles (wider net)
Roles the profile supports but that are not the primary target: mobile, devrel, solutions engineering, small-team engineering management.
Titles: Senior React Native Engineer, Senior Mobile Engineer, Developer Relations Engineer, Developer Experience Engineer, Solutions Engineer (wallet/custody), Engineering Manager (protocol/platform), Technical Lead
```
site:linkedin.com/jobs "react native" senior engineer New York OR remote
site:linkedin.com/jobs "developer relations engineer" blockchain OR infrastructure
site:linkedin.com/jobs "solutions engineer" wallet OR custody OR passkeys
site:linkedin.com/jobs "engineering manager" protocol OR blockchain New York
```
## Location Filter
- **Ideal:** New York City (any borough; onsite, hybrid, or remote); San Francisco / Bay Area onsite; fully remote (US)
- **Acceptable:** any US city with relocation (candidate is open to relocation); remote-global
- **Borderline:** onsite outside NYC/SF with no relocation support; hybrid requiring more than occasional travel to a non-NYC/SF office
- **Too far:** onsite outside the US; roles requiring non-US work authorization
Candidate is a US citizen; no visa constraints. Available from **mid-to-late September 2026**.
## Hard Filters (beyond location)
- **AI tooling must be allowed.** Drop or flag any posting that prohibits AI-assisted development (Claude Code, Copilot, etc.); this is a deal-breaker, not a preference.
- Target senior / founding-level roles; skip junior, mid, and intern postings unless the title is "Founding Engineer". Staff/Principal postings are kept but flagged as a level stretch (no Staff IC experience).
## Language Filter
Working languages are in CLAUDE.md's Languages table (English native, Telugu). Apply `04-job-evaluation.md`'s Language Gate: a posting requiring a language not declared is excluded; a higher-than-declared level in a declared language is flagged, not excluded.
## Date Filter
Live portals: only jobs posted within the last 14 days, or with an open deadline. `jobsdb-search`: within 30 days of the row's snapshot date, and URL-verified live. If a posting date cannot be determined, include it but flag as "date unknown".
## Adapting Queries
If the user specifies a focus area, select queries from the matching category and also generate 2-3 custom queries for that focus. For example:
- `/scrape founding` -> Priority 2 queries + `jobsdb-search -q "founding" --tier 2`
- `/scrape crypto` -> Priority 3 + `jobsdb-search --crypto-native --seniority senior,staff_principal`
- `/scrape anthropic` -> `jobsdb-search --company Anthropic` + `site:job-boards.greenhouse.io/anthropic`