cv-2026/.claude/skills/triage-jobs/SKILL.md

17 KiB
Raw Blame History

name description
triage-jobs Triage one or more job vacancies using Oleg's canonical apply/skip playbook (vacancy-filter-and-triage-2026.md). Applies 4 hard gates, classifies the CV track, runs the research protocol, checks for prior applications, and outputs a consolidated verdict. For batch runs from telegram_inbox.json, stratifies by priority and delegates p2/p3 bulk to a Haiku subagent to protect the main session.

triage-jobs

Operational implementation of base/reference/vacancy-filter-and-triage-2026.md. That file is the authoritative playbook — read it before running this skill if you don't have it in context.

Posture: trying-friendly, not filtering-friendly. Hard skip is rare and requires a concrete objective blocker. On every ambiguous case, default to "подаём".


When to use

  • Oleg provides a job posting URL, raw JD text, or says "разбери вакансию".
  • After running scripts/fetch_telegram_jobs.py — triage tracking/telegram_inbox.json.
  • Any "should we apply?" question about a specific role.

Input modes

Mode What Oleg provides
Single URL One job posting link — fetch, triage, output verdict
Raw JD Pasted text — triage directly
Batch (inbox) No specific job — triage all entries in tracking/telegram_inbox.json

Step 0 — Read the playbook (if needed)

If vacancy-filter-and-triage-2026.md is not already in context:

Read: base/reference/vacancy-filter-and-triage-2026.md

Step 1 — Fetch the JD

For URL input:

  1. Try WebFetch first.
  2. If blocked (403, bot wall, login wall, JS-only, empty body) → switch to chrome-session skill immediately, no confirmation needed.
  3. If neither works → halt and report. Never reconstruct a JD from memory.

For Greenhouse / Lever / Ashby / Freshteam ATS: SSR usually works; confirm the "Apply" button is active (not a JS overlay saying "not accepting applications").


Step 2 — Extract fast facts (< 1 min)

Pull from the JD:

  • Role title and seniority signals
  • Must-have stack (vs. nice-to-have)
  • AI involvement: must-have / nice-to-have / not mentioned
  • Location & remote policy
  • Timezone / overlap requirements (explicit or inferable from location)
  • Compensation signals (range, equity framing, "scrappy" language, funding stage)

Step 3 — Run 4 hard gates

Evaluate gates in order. First failure → SKIP with one objective reason. All four pass → continue.

Gate 1 — Geography / hiring

Passes if the role can hire Oleg via any of:

  • Global remote (non-US/UK/EU-restricted)
  • Contractor via Deel or equivalent EOR covering Thailand
  • B2B contract for remote work
  • Russian-speaking team / Russian-speaking founders, hiring remotely (excluding RU/BY companies explicitly requiring presence)
  • Russian companies hiring remotely (presence in RU not required)
  • Relocation offered AND comp ≥ $10k/month
  • Temporary contract fully geography-agnostic (Mira, Algery, similar aggregators)

Skip if: location-locked / US-only / UK-only / EOR doesn't cover Thailand and no contractor option.

Gate 2 — Timezone (comp-scaled, not binary)

Oleg's flexible window: 9:0022:00 GMT+7. Comfortable day: 11:0020:00.

Required overlap Pass condition
Fits within 9:0022:00 GMT+7 Passes at any comp ≥ floor
Partially outside the window Passes if comp > $9k
Full US-day / PST core hours / night work Passes if comp > $10k

Skip only if: comp ≤ $9k AND required overlap falls outside 9:0022:00 GMT+7.

Gate 3 — Compensation

Tier Range Treatment
Target $812k/month Main funnel
Floor $68k/month Normal lower bound
Fallback $4.56k/month Temporary bridge only — штучно и осознанно
Skip < $4.5k/month Hard skip

No explicit comp? Read signals: pre-seed + equity-heavy framing + "scrappy team" → likely below floor. Founding-engineer at Series AB AI startup can stretch to $15k+ if profile matches.

Gate 4 — Seniority

Passes for: Senior / Staff / Tech Lead / experienced middle+.

  • Skip if: explicitly Junior or mid with no growth ceiling.
  • Do NOT skip if: role title is Lead / Principal / Head of — treat as "they don't have that person internally, try it."
  • Do NOT skip if: role looks like middle but comp fits floor/target AND the role offers interesting new experience (AI, client-facing, English immersion).

Step 4 — Anti-pattern self-check

Before marking skip, run through the false-blocker list. If you're about to skip on any of these, escalate to the next tier instead:

  1. Role looks senior-heavy (Lead/Principal/Head) → that's a try signal.
  2. One missing skill → ask Oleg first (see Step 6), not skip.
  3. "Not an AI-first company" → Track B or Hybrid, not skip.
  4. Superficial domain mismatch → look at actual technical tasks (orchestration, LLM workflows, structured extraction).
  5. "Not a Russian-speaking scale-up" → neutral, not negative.
  6. Timeline outside 8-week window → affects sequencing, not eligibility.
  7. Comp below $8k but in floor/fallback → consider, especially strategic or interesting roles.
  8. Tech stack mismatch with fast ramp → lean toward apply, mention ramp honestly.
  9. Doesn't fully pass triage but ≥50% match + low application effort (LinkedIn Easy Apply, simple form, DM) → quick-apply mode: note it explicitly as "fast/training apply."

Step 5 — Research protocol

Run for all roles that cleared the gates (and for borderline cases to resolve ambiguity).

5.0 — Check for prior application

Before deep research, check whether Oleg already applied to this role or company. Trello is the source of truth (cards may sit in any column depending on stage):

  • Trello (primary)mcp__trello__set_active_board(boardId="6a1a9a5af082cb0526b22704"), then get_cards_by_list_id for the vacancy-bearing lists and match the company against card titles:
    • TODO 6a1aa59555aab72a261c42aa · In Progress 6a1aa59a5e7e651b1352895b · In touch 6a267e2e9ed02737346dc047 · Applyed 6a1aa5a1d8bcb0ed7234987b · Rejected 6a245a328452976c0d1fcca1
    • Include Rejected too — a previously-rejected company still counts as "already processed", don't re-surface it as new.
    • Extract only each card's name + shortUrl — don't carry the full card JSON forward (see the context note in the batch pre-step).
  • Telegram — search in job-tracking chats if applicable.
  • Gmail — search for company name if accessible.
  • tracking/applications.md is frozen/legacy — kept for backward compatibility only. Do not read or write it.

If already applied → note it in the output, don't re-triage as new.

5.1 — Research the company

What the company does, product, stage, funding, team size, recent news, reputation (Glassdoor / Blind if available). Goal: context + ghost/red-flag filter.

5.2 — Cross-platform posting check

Find the same vacancy on: company careers page, LinkedIn, ATS link (Greenhouse/Lever/Ashby/etc.), job boards, aggregators. Consolidate:

  • Posting date — earliest appearance. 90+ days without update = ghost signal.
  • Comp range — different platforms often show different bands. Collect all.
  • Geo restrictions — any platform saying US Only / UK Only / location-locked / eligible countries list?

5.3 — Restriction priority rule

Restrictions are authoritative. If one source says "remote" and another says "US only" → US only wins → Gate 1 skip. Less restrictive wording wins only if it explicitly and clearly overrides/removes the restriction.


Step 6 — Missing skills

One missing skill is not a skip if it isn't flagged as explicit MUST HAVE.

Action: ask Oleg before applying — "JD asks for X, I don't see it in your materials — did you have any experience with it?" Many skills exist but were trimmed from CV (examples: Redux Toolkit/RTK Query, Vitest, Playwright, Jest, eCommerce/CMS work).

Real gaps — can name without checking: Go, Rust, Java, C++, Ruby, PHP, heavy DevOps (K8s orchestration at scale, SRE), native mobile (Swift, Kotlin). React Native and Python are bridgeable, not gaps.

Never flag a gap explicitly in a cover letter — don't write "one thing to flag" or "I notice a gap here."


Step 7 — Classify track (for roles that cleared gates)

JD condition Track CV file
AI in must-have: LLM / agents / MCP / RAG / orchestration / embeddings in core requirements Track A base/cv-2026-05-base.md
Pure Node.js / backend, AI as nice-to-have or "significant plus" Backend-first base/cv-backend-variant-hostinger-2026-05.md
Senior / full-stack / frontend, no AI in must-have Track B base/oleg_proskurin_fullstack_techlead_cv.md
Senior + AI-tooling fluency as requirement (build with AI assistance, not AI product) Hybrid CV-B with AI moved up 23 positions in Skills

Russian-speaking scale-ups (Manychat, inDrive, Wheely, Joom, Adapty, similar) → Track B by default, even if AI not mentioned.

Tiebreakers:

  • Track B vs. skip → Track B wins.
  • Track A vs. Track B for hybrid JD → AI in must-have → A; otherwise → B.

Step 8 — Output format

One consolidated block per vacancy. No separate research section — merge everything.

## [Company] — [Role Title]

**Verdict:** APPLY (Track A) | SKIP | QUICK-APPLY

**Gate check:**
- Gate 1 (Geo): ✅ global remote / ❌ US-only
- Gate 2 (TZ): ✅ EMEA overlap fits / ⚠️ partial, comp > $9k ok / ❌
- Gate 3 (Comp): ✅ $XYk (target) / ⚠️ floor / ❌
- Gate 4 (Level): ✅ Senior / ❌ Junior

**Track:** A / B / Backend-first / Hybrid — CV: [filename]

**Strong matches:** [24 bullets: specific tech/scope from JD that map directly to Oleg's background]

**Open questions:** [missing skills to ask Oleg about; 12 items max]

**Research notes:**
- Company: [stage, product, funding, red flags if any]
- Posting date: [earliest found] — [fresh / stale signal]
- Comp across platforms: [consolidate bands found]
- Geo/restrictions: [what each platform says; which is authoritative]

**Prior application:** none found / ⚠️ already applied on [date], see Trello card [name]

**Proof-points for letter:** [23 concrete bullets that directly answer the JD's key asks]

For SKIP: one-liner after gate check explaining the objective blocker. No research section needed unless it was needed to determine the gate outcome.


Batch mode — stratified triage from telegram_inbox.json

When triaging tracking/telegram_inbox.json:

Pre-step: build prior-applications list (do this once before spawning any subagent)

Trello is the source of truth. Build known_applied from card titles in the vacancy-bearing lists:

mcp__trello__set_active_board(boardId="6a1a9a5af082cb0526b22704")
mcp__trello__get_cards_by_list_id(listId="6a1aa59555aab72a261c42aa")   # TODO
mcp__trello__get_cards_by_list_id(listId="6a1aa59a5e7e651b1352895b")   # In Progress
mcp__trello__get_cards_by_list_id(listId="6a267e2e9ed02737346dc047")   # In touch
mcp__trello__get_cards_by_list_id(listId="6a1aa5a1d8bcb0ed7234987b")   # Applyed
mcp__trello__get_cards_by_list_id(listId="6a245a328452976c0d1fcca1")   # Rejected

From the results keep only each card's name (the "Company — Role" title); discard everything else. That titles-only list is known_applied — pass it into every Haiku subagent prompt (see template). Match new vacancies by company-name substring. (Rejected is included on purpose — a rejected company is still "already processed".)

Context safety (important). This MCP has no full-text card search and no field-limited fetch — get_cards_by_list_id returns the full ~1.5 KB JSON per card. So:

  • Fetch only the five vacancy lists above (not Base / Boards / Artifactes), and do it once per run.
  • Distill to titles immediately; never feed full card JSON into a subagent.
  • When the Applyed list grows large (hundreds of cards), use the lean equivalent instead — one Trello REST call returning names only:
    source .env && curl -s "https://api.trello.com/1/boards/6a1a9a5af082cb0526b22704/cards?fields=name,idList&filter=visible&key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" \
      | jq -r '.[].name'
    
    This returns every card name on the board in one compact payload — the direct successor to the old applications.md grep.

tracking/applications.md is frozen/legacy — no longer read for dedup.

Stratification rules

  1. Read the file. Group entries by channel priority field (p1 / p2 / p3).
  2. All p1, p2, and p3 entries: delegate to a Haiku subagent first pass.
    • Pass the raw entries, the playbook summary, and the known_applied list to the subagent.
    • Subagent returns a structured verdict list (APPLY / SKIP / QUICK-APPLY / VERIFY).
    • SKIP verdicts from Haiku are accepted without re-review.
  3. Main session reviews only APPLY and VERIFY verdicts from Haiku — runs full Step 18 per entry.
  4. After all verdicts: promote confirmed APPLY entries to Trello (a card per application — see "After triage — update tracking"). Never write to tracking/applications.md.
  5. For QUICK-APPLY entries: note explicitly, ask Oleg if he wants to proceed.
  6. For VERIFY entries: run a fast WebSearch "{company} remote hiring countries" to resolve geo; then apply the gate.

Why Haiku-first for everything: p1 channels can have 50100+ messages per run. Sending them straight to the main session risks context overflow and losing genuine APPLY roles in the flood. Haiku filters down to a manageable APPLY+VERIFY set; main session then does quality deep-dive on only those. A few extra false VERIFYs from Haiku are fine — main session catches them. Missing a real APPLY because main session ran out of context is not fine.

Haiku subagent prompt template

You are triaging job vacancies for Oleg Proskurin (Tech Lead / AI Engineer, based in Thailand GMT+7, available for remote work).

Apply these 4 hard gates — SKIP on first failure:

Gate 1 (Geo): Must allow: global remote non-US/UK/EU, contractor via Deel/EOR from Thailand, B2B remote, Russian-speaking teams hiring remotely (not RU/BY presence-required), relocation with comp ≥$10k, geography-agnostic temp contracts.
Gate 2 (TZ): Oleg's window 9:0022:00 GMT+7. Outside window: ok if comp > $9k. Night/PST core: ok if comp > $10k. Skip only if comp ≤ $9k AND overlap is outside window.
Gate 3 (Comp): Skip if < $4.5k/month. Floor $68k, target $812k.
Gate 4 (Level): Skip if explicitly Junior or mid with no growth ceiling.

Posture: trying-friendly when data IS PRESENT and borderline. When key gate data is ABSENT from the message text, return VERIFY — not APPLY. Lean toward APPLY only when geo and comp are explicitly stated and borderline (e.g. "global remote", "$7k/month").

Geo rules (Gate 1):
- Message explicitly says "global remote" / "worldwide" / "anywhere" / "любой город" / contractor-friendly → Gate 1 passes, proceed.
- Message says "United States" / "US only" / "United Kingdom only" / specific country list excluding Thailand → SKIP Gate 1.
- Message says "Remote" with no geo qualifier → VERIFY geo (cannot confirm Thailand eligibility without JD).
- Message has no location info at all → VERIFY geo.

Comp rules (Gate 3):
- Explicit salary ≥ $4.5k/month or ≥ $54k/year → Gate 3 passes, proceed.
- Explicit salary < $4.5k/month → SKIP Gate 3.
- No salary mentioned → VERIFY comp.
- Timezone offset explicitly stated (e.g. GMT-GMT+6) → treat as geo restriction, apply Gate 1.

Known applied companies — flag these as SKIP (already applied):
[INSERT known_applied LIST HERE — one line per company]

For each vacancy below, output one line:
APPLY | SKIP | QUICK-APPLY | VERIFY — [company/role] — [gate failed or what's missing] — [track: A/B/Backend-first/Hybrid]

Vacancies:
[paste entries from telegram_inbox.json]

After triage — update tracking

A Trello card is the application record (this replaces the old applications.md row — that file is frozen/legacy, do not write to it). For every APPLY verdict, create one card in the TODO column (6a1aa59555aab72a261c42aa) on board 6a1a9a5af082cb0526b22704:

  • Title: Company — Role (Track X)
  • Column = status. The funnel is the columns: TODO (to apply) → In Progress (applying) → In touch (recruiter contact / screening) → Applyed (submitted); Rejected (closed). New cards land in TODO; Oleg moves them as stages change.
  • Description: the triage verdict block, plus a Tracking footer carrying everything the old applications.md row held:
    ---
    **Tracking**
    - Date added: <YYYY-MM-DD>
    - Channel: <e.g. TG @serbia_jobs · direct (Ashby) · LinkedIn>
    - CV used: <track> — <path to pdf>
    - JD: <url>
    - Status: <todo | applied YYYY-MM-DD via …>
    

For SKIP: no card needed unless Oleg says to keep it for reference.