--- name: network-scan description: Scan your LinkedIn contacts' companies for matching job openings argument-hint: "number of contacts (default 25) or 'all'" --- # Network Scan Skill > **Priority hierarchy**: See `shared/references/priority-hierarchy.md` for conflict resolution. Proactively check whether companies where you know someone are hiring for roles that match you. First run builds a cache of company careers page URLs. Subsequent runs reuse the cache, making weekly checks fast. ## Quick Start - `/proficiently:network-scan` - Scan companies from your 25 most recent contacts - `/proficiently:network-scan 50` - Check the 50 most recent contacts - `/proficiently:network-scan all` - Check all contacts (can be slow with 1500+) ## File Structure ``` scripts/ resolve-careers.md # Subagent for resolving a batch of company careers URLs evaluate-company.md # Subagent for scanning a batch of companies' open roles ``` User data (stored at ~/.proficiently/): ``` ~/.proficiently/ resume/ # Your resume PDF/DOCX preferences.md # Job matching rules profile.md # Work history from interview linkedin-contacts.csv # LinkedIn contacts export company-careers.json # Cached company careers URLs network-scan-history.md # Running log of scan results jobs/ # Per-job application folders ``` --- ## Workflow ### Step 0: Check Prerequisites Resolve the data directory, then check prerequisites per `shared/references/prerequisites.md`. Resume, preferences, and linkedin-contacts.csv are all required. Load these files for use in later steps: - `DATA_DIR/preferences.md` (target roles, must-haves, dealbreakers, nice-to-haves) - `DATA_DIR/resume/*` (candidate profile) - `DATA_DIR/profile.md` (work history, if it exists) ### Step 1: Select Contacts & Extract Companies Parse `$ARGUMENTS`: - If a number (e.g., `50`): use that as the contact limit - If `all`: use all contacts (warn user this may be slow if > 200) - If empty/missing: default to 25 Read `~/.proficiently/linkedin-contacts.csv`. Sort by "Connected On" descending (most recent first). Take the first N contacts based on the limit. Extract unique company names from the selected contacts. Skip companies with empty or blank names. Group contacts by company into a lookup: ``` { "Google": [{"name": "Jane Smith", "position": "PM Director", "url": "https://linkedin.com/in/janesmith"}, ...], "Stripe": [{"name": "John Doe", "position": "Eng Manager", "url": "https://linkedin.com/in/johndoe"}] } ``` Report to user: "Found X unique companies from Y contacts. Checking careers pages..." ### Step 2: Resolve Careers Pages (Parallelized) Load `~/.proficiently/company-careers.json` if it exists (the cache). If it doesn't exist, start with an empty object. Split companies into three groups: - **Cached (fresh)**: `last_checked` within last 7 days - use as-is, no work needed - **Cached (stale)**: `last_checked` older than 7 days - needs re-verification - **Uncached**: not in cache, or `type` is `"not_found"` and stale - needs full resolution Report: "X companies from cache, Y need resolution..." **Parallel resolution using subagents:** Take all companies needing resolution (stale + uncached) and split them into batches of 10. Spawn one subagent per batch using the Task tool (`subagent_type: "general-purpose"`). Run all batches in parallel. Each subagent receives: - A batch of company names to resolve - Instructions from `scripts/resolve-careers.md` Each subagent uses `WebSearch` (NOT the browser) to find careers pages: 1. Search: `"[Company Name]" careers jobs site:[company domain if known]` 2. From the search results, identify the careers/jobs page URL 3. Classify the URL type: - `"direct"` - company's own careers page (e.g., careers.google.com) - `"greenhouse"` - Greenhouse ATS (boards.greenhouse.io/company or company.greenhouse.io) - `"lever"` - Lever ATS (jobs.lever.co/company) - `"workday"` - Workday ATS (company.wd5.myworkdayjobs.com) - `"other_ats"` - other ATS platforms (Ashby, BambooHR, etc.) - `"not_found"` - no careers page could be found (set `careers_url` to null) 4. Return results for the batch Collect results from all subagents and merge into the cache. Save `~/.proficiently/company-careers.json`. Format: ```json { "Company Name": { "careers_url": "https://careers.example.com", "type": "direct", "last_checked": "YYYY-MM-DD", "last_found_roles": 0 } } ``` Report progress: "Resolved X new careers pages, Y from cache, Z not found." ### Step 3: Scan for Matching Jobs (Parallelized) Take all companies with a valid `careers_url` (skip `not_found` and `ignored` entries). Split them into batches of 5 companies each. **Spawn parallel subagents** using the Task tool (`subagent_type: "general-purpose"`). Run all batches in parallel (up to 5 concurrent subagents to avoid overwhelming the browser). Each subagent receives: - A batch of companies (name, careers_url, ATS type, network contacts) - Candidate profile summary (from resume) - Preferences (target roles, must-haves, dealbreakers, nice-to-haves) - Instructions from `scripts/evaluate-company.md` Each subagent: 1. Creates its own browser tab (`tabs_context_mcp` then `tabs_create_mcp`) 2. For each company in its batch: a. Navigate to the careers page b. Search/browse for roles matching target roles and keywords c. For ATS pages, use platform search/filter functionality: - **Greenhouse**: search box or department filters - **Lever**: search bar or team filter - **Workday**: keyword search field - **Direct/other**: browse the page, use any search, scan listed roles d. Extract listings: title, location, URL e. Score each listing (High/Medium/Low/Skip per fit criteria) f. Return only High and Medium fits 3. Returns results for its entire batch **Fit scoring criteria:** See `shared/references/fit-scoring.md` for the canonical definitions. Collect results from all subagents. Update `last_found_roles` count in the cache for each company scanned. If a subagent fails or times out, log the companies it was processing and move on. Do not retry - the user can re-run with those companies next time. ### Step 4: Save Results **Update company-careers.json:** Update `last_checked` and `last_found_roles` for every company that was scanned. **Append to `~/.proficiently/network-scan-history.md`:** If the file doesn't exist, create it with: ```markdown # Network Scan History This file tracks all network scans run by the `/network-scan` skill. --- ``` Then append: ```markdown ## YYYY-MM-DD - Network Scan (N contacts, M companies) | Company | Contact | Role Found | Fit | URL | |---------|---------|------------|-----|-----| | Google | Jane Smith (PM Director) | Sr. Product Manager | High | https://... | | Stripe | John Doe (Eng Manager) | No matching roles | - | - | ``` Include all companies scanned (both matches and non-matches) in the table. **Save full postings for High-fit matches:** For each High-fit match, navigate to the job posting URL and save the full posting to `~/.proficiently/jobs/[company-slug]-[YYYY-MM-DD]/posting.md` using the standard format: ```markdown # [Job Title] - [Company Name] **Company**: [Company] **Location**: [Location] **Salary**: [Salary or N/A] **Type**: [Type] **Source**: network-scan **Date Found**: YYYY-MM-DD **Network Contact**: [Contact Name] ([Position]) - [LinkedIn URL] ## About the Role [Description] ## Key Requirements - [requirement] ## Direct Careers Page - [URL] ## Fit Assessment **Rating**: [High/Medium] **Why**: [explanation] ``` ### Step 5: Present Results Show matches grouped by fit, with contact info for warm introductions: ```markdown ## Network Scan Results - YYYY-MM-DD Scanned N companies from M contacts. ### Matches Found #### 1. Senior Product Manager at Google - **Fit**: High - **Your contact**: Jane Smith (PM Director) - [LinkedIn](url) - **Location**: Mountain View, CA - **Apply**: https://careers.google.com/jobs/... - **Why**: [brief match reason] #### 2. Strategy Lead at Stripe - **Fit**: Medium - **Your contact**: John Doe (Eng Manager) - [LinkedIn](url) - **Location**: Remote - **Apply**: https://stripe.com/jobs/... - **Why**: [brief match reason] ### Companies Checked (No Matches) Google (3 open roles, none matching), Stripe (0 open roles), ... ### Companies Without Careers Pages Acme Corp, Small Startup LLC, ... ``` If no matches were found across all companies: ```markdown ## Network Scan Results - YYYY-MM-DD Scanned N companies from M contacts. No matching roles found this time. ### Companies Checked [List with role counts] ### Companies Without Careers Pages [List] Try again next week, or expand your search: `/proficiently:network-scan 100` ``` End with: ``` To tailor a resume: /proficiently:tailor-resume [job URL] To write a cover letter: /proficiently:cover-letter [job URL] Built by Proficiently. Want someone to find jobs, tailor resumes, apply, and connect you with hiring managers? Visit proficiently.com ``` ### Step 6: Learn from Feedback If the user provides feedback after seeing results: - **"Skip [company]"**: Add `"ignored": true` to that company's entry in company-careers.json. Future scans will skip it. - **Corrects a careers URL**: Update the cache entry with the correct URL and type. - **Adjusts preferences**: Update `~/.proficiently/preferences.md` accordingly (e.g., "add fintech to nice-to-haves", "no crypto companies"). --- ## Response Format Structure user-facing output with these sections: 1. **Network Matches** — list of High/Medium fits with company, role, fit rating, contact name, and apply URL 2. **Companies Checked** — summary of companies with no matches and companies without careers pages 3. **Next Steps** — suggest `/proficiently:tailor-resume` and `/proficiently:cover-letter` for top matches --- ## Permissions Required Add to `~/.claude/settings.json`: ```json { "permissions": { "allow": [ "Read(~/.claude/skills/**)", "Read(~/.proficiently/**)", "Write(~/.proficiently/**)", "Edit(~/.proficiently/**)", "mcp__claude-in-chrome__*" ] } } ```