275 lines
17 KiB
Markdown
275 lines
17 KiB
Markdown
# Career-Ops -- AI Job Search Pipeline
|
|
|
|
## Origin
|
|
|
|
This system was built and used by [santifer](https://santifer.io) to evaluate 740+ job offers, generate 100+ tailored CVs, and land a Head of Applied AI role. The archetypes, scoring logic, negotiation scripts, and proof point structure all reflect his specific career search in AI/automation roles.
|
|
|
|
The portfolio that goes with this system is also open source: [cv-santiago](https://github.com/santifer/cv-santiago).
|
|
|
|
**It will work out of the box, but it's designed to be made yours.** If the archetypes don't match your career, the modes are in the wrong language, or the scoring doesn't fit your priorities -- just ask. You (AI Agent) can edit the user's files. The user says "change the archetypes to data engineering roles" and you do it. That's the whole point.
|
|
|
|
## Data Contract (CRITICAL)
|
|
|
|
There are two layers. Read `DATA_CONTRACT.md` for the full list.
|
|
|
|
**User Layer (NEVER auto-updated, personalization goes HERE):**
|
|
- `cv.md`, `config/profile.yml`, `modes/_profile.md`, `modes/_custom.md`, `article-digest.md`, `portals.yml`
|
|
- `data/*`, `reports/*`, `output/*`, `interview-prep/*`
|
|
|
|
**System Layer (auto-updatable, DON'T put user data here):**
|
|
- `modes/_shared.md`, `modes/oferta.md`, all other modes
|
|
- `CLAUDE.md`, `*.mjs` scripts, `dashboard/*`, `templates/*`, `batch/*`
|
|
|
|
**THE RULE: When the user asks to customize anything, write to the USER layer, NEVER to a system file — that is what survives `node update-system.mjs`.**
|
|
- **Profile / evaluation content** (archetypes, narrative, negotiation scripts, proof points, location policy, comp targets) → `modes/_profile.md` or `config/profile.yml`.
|
|
- **Procedural rules** (house rules, custom workflows, output preferences, "always/never do X" automations) → `modes/_custom.md` (create it from `modes/_custom.template.md` if missing).
|
|
- **NEVER** edit `modes/_shared.md`, `CLAUDE.md`, or any other system file for user-specific content — those get overwritten on update.
|
|
|
|
## Source-of-Truth Boundary (CRITICAL)
|
|
|
|
User-facing content (CV, cover letters, application emails, form answers, recruiter outreach, application form responses) is generated **exclusively** from these files plus statements the user makes directly in the current conversation:
|
|
|
|
- `cv.md`
|
|
- `article-digest.md`
|
|
- `config/profile.yml`
|
|
- `modes/_profile.md`
|
|
- `writing-samples/`
|
|
- `voice-dna.md` (voice/style only — governs *how* text reads, never introduces factual claims)
|
|
- `interview-prep/story-bank.md` and `interview-prep/{company}-{role}.md` (the user's own STAR stories and interview-prep notes — same trust level as `cv.md`; consumed by the `interview` and `apply`/`match-star` modes)
|
|
|
|
Anything not in this list is **out of scope for content generation**, including:
|
|
|
|
- Auto-memory at `~/.claude/projects/.../memory/` — see scope clarification below
|
|
- Any directory outside the career-ops project — for example, parent-directory repos containing the user's product code, sibling project directories, or other unrelated codebases on the same machine
|
|
- Cross-session inferences about the user's work that have not been written into one of the in-scope files
|
|
- Knowledge from other Claude Code projects on the same machine
|
|
|
|
**Rule from the original design (santifer's case study):** *"Keywords get reformulated, never fabricated."* Reorder, reframe, emphasise — but never invent. If a claim isn't backed by an in-scope file, ask the user. If they cannot or do not want to add it, the output goes without it. Silence on a topic is fine; manufactured detail is not.
|
|
|
|
**Authorship claims are non-negotiable.** Never claim the user authored a project, repo, library, tool, framework, or open-source artefact unless explicitly attributed to them in `cv.md` or `article-digest.md`. Tool-of-trade conflation (the user uses X → the user built X) is the most common fabrication pattern and is explicitly forbidden.
|
|
|
|
### Auto-memory scope (clarification, not exception)
|
|
|
|
The auto-memory layer at `~/.claude/projects/.../memory/` is reserved for **behavioural steering only**:
|
|
|
|
- User preferences (style, tone, formatting, communication cadence)
|
|
- Process rules and corrections (don't do X, always do Y)
|
|
- Operational state (active relationships, applied roles, observed patterns, outcome learnings)
|
|
- External references (where to find things in other systems)
|
|
|
|
Auto-memory **never** holds content claims about the user's work, technical accomplishments, authorship, or anything that would appear verbatim or near-verbatim in CV/cover output. If a fact belongs in user-facing content, it lives in the user-layer files, not in memory.
|
|
|
|
### Where rules live
|
|
|
|
Rules belong in files the harness reads automatically — `CLAUDE.md`, `AGENTS.md`, `modes/*.md`, `MEMORY.md`. Do not create sidecar documentation that requires manual loading. Reinforcement-without-enforcement decays.
|
|
|
|
## Update Check
|
|
|
|
On the first message of each session, run the update checker silently:
|
|
|
|
```bash
|
|
node update-system.mjs check
|
|
```
|
|
|
|
Parse the JSON output:
|
|
- `{"status": "update-available", "local": "1.0.0", "remote": "1.1.0", "changelog": "..."}` → tell the user:
|
|
> "career-ops update available (v{local} → v{remote}). Your data (CV, profile, tracker, reports) will NOT be touched. Want me to update?"
|
|
If yes → run `node update-system.mjs apply`. If no → run `node update-system.mjs dismiss`.
|
|
- `{"status": "up-to-date"}` → say nothing
|
|
- `{"status": "dismissed"}` → say nothing
|
|
- `{"status": "offline"}` → say nothing
|
|
- `{"status": "no-remote-version"}` → say nothing (checker reached GitHub but neither VERSION nor the latest release tag parsed as semver — treat as a silent non-failure, same as offline)
|
|
|
|
The user can also say "check for updates" or "update career-ops" at any time to force a check.
|
|
To rollback: `node update-system.mjs rollback`
|
|
|
|
## What is career-ops
|
|
|
|
AI-powered job search automation built on Claude Code: pipeline tracking, offer evaluation, CV generation, portal scanning, batch processing.
|
|
|
|
### Main Files
|
|
|
|
| File | Function |
|
|
|------|----------|
|
|
| `data/applications.md` | Application tracker |
|
|
| `data/pipeline.md` | Inbox of pending URLs |
|
|
| `data/scan-history.tsv` | Scanner dedup history |
|
|
| `portals.yml` | Query and company config |
|
|
| `templates/cv-template.html` | HTML template for CVs |
|
|
| `templates/cv-template.tex` | LaTeX/Overleaf template for CVs |
|
|
| `generate-pdf.mjs` | Playwright: HTML to PDF |
|
|
| `generate-latex.mjs` | LaTeX CV validator + pdflatex compiler |
|
|
| `article-digest.md` | Compact proof points from portfolio (optional) |
|
|
| `interview-prep/story-bank.md` | Accumulated STAR+R stories across evaluations |
|
|
| `interview-prep/{company}-{role}.md` | Company-specific interview intel reports |
|
|
| `analyze-patterns.mjs` | Pattern analysis script (JSON output). Includes ATS channel analysis (per-vendor advance rate; motivated by Bommasani et al., Algorithmic Monocultures in Hiring, FAccT 2026). |
|
|
| `followup-cadence.mjs` | Follow-up cadence calculator (JSON output) |
|
|
| `followup-seed.mjs` | Seeds `data/follow-ups.md` with a pinned first follow-up date when a row turns Applied (JSON output) |
|
|
| `data/follow-ups.md` | Follow-up history tracker |
|
|
| `scan.mjs` | Zero-token portal scanner — hits Greenhouse/Ashby/Lever APIs directly, zero LLM cost |
|
|
| `check-liveness.mjs` | Job posting liveness checker |
|
|
| `liveness-core.mjs` | Shared liveness logic (expired signals win over generic Apply text) |
|
|
| `reports/` | Evaluation reports (format: `{###}-{company-slug}-{YYYY-MM-DD}.md`). Blocks A-F + G (Posting Legitimacy), plus `## Machine Summary` YAML for downstream scripts. Header includes `**Legitimacy:** {tier}`. |
|
|
|
|
### OpenCode, Antigravity CLI & Grok Build CLI Commands
|
|
|
|
[OpenCode](https://opencode.ai), Antigravity CLI, and Grok Build CLI natively support the open agent skill standard (`agentskills.io`).
|
|
|
|
Instead of registering individual `.toml` files for every slash command, all subcommands are routed through the single unified skill defined in `.agents/skills/career-ops/SKILL.md`.
|
|
|
|
You can invoke the command center or any of its modes directly within your CLI:
|
|
|
|
* `/career-ops` (Shows the Command Center menu)
|
|
* `/career-ops {JD text or URL}` (Runs the auto-evaluation pipeline)
|
|
* `/career-ops [subcommand]` (Runs a specific subcommand)
|
|
|
|
#### Subcommands:
|
|
* `pipeline` — Process pending URLs from inbox
|
|
* `scan` — Scan job portals for new offers
|
|
* `tracker` — Show application status overview
|
|
* `pdf` — Generate ATS-optimized CV PDF
|
|
* `latex` — Export CV as LaTeX/Overleaf .tex
|
|
* `cover` — Generate cover letter
|
|
* `email` — Draft formal application email only; never sends, submits, or clicks
|
|
* `interview-prep` — Generate interview preparation guide
|
|
* `interview` — Onboarding/on-demand interview
|
|
* `contacto` — Generate LinkedIn outreach message
|
|
* `deep` — Execute deep company research
|
|
* `training` — Evaluate course/cert against North Star
|
|
* `project` — Evaluate portfolio project idea
|
|
* `batch` — Run parallel batch evaluations
|
|
* `patterns` — Analyze rejection patterns
|
|
* `followup` — Update and calculate follow-ups
|
|
* `update` — Update system files
|
|
|
|
All `modes/*` files and prompt contexts are shared across Claude Code, OpenCode, Antigravity CLI, and Grok Build CLI. `GEMINI.md` remains only as a legacy no-op guard so Antigravity does not duplicate the full project instructions.
|
|
|
|
### First Run — Onboarding (IMPORTANT)
|
|
|
|
**Before doing ANYTHING else, check if the system is set up.** On the first message of each session, run the cold-start check — one deterministic source of truth (this doc and `doctor.mjs` share the same prerequisite list, so they can never drift):
|
|
|
|
```bash
|
|
node doctor.mjs --json
|
|
```
|
|
|
|
Output: `{"onboardingNeeded": <bool>, "missing": [...], "warnings": [...], "autoCopied": [...]}`, where `missing` lists whichever of `cv.md`, `config/profile.yml`, `modes/_profile.md`, `portals.yml` are absent. `warnings` is reserved for non-blocking setup signals, and `autoCopied` lists user customization files (`modes/_profile.md` or `modes/_custom.md`) that `doctor.mjs` automatically copied from their `.template.md` equivalents during the check.
|
|
|
|
**If `onboardingNeeded` is true (any of `cv.md` / `config/profile.yml` / `modes/_profile.md` / `portals.yml` is missing), enter onboarding mode.** Do NOT proceed with evaluations, scans, or any other mode until the basics are in place. Invoke the `onboarding-wizard` skill for the exact step-by-step script (CV, profile, portals, tracker, getting to know the user, and the "ready" handoff).
|
|
|
|
### Personalization
|
|
|
|
This system is designed to be customized by YOU (AI Agent). When the user asks you to change archetypes, translate modes, adjust scoring, add companies, or modify negotiation scripts -- do it directly. You read the same files you use, so you know exactly what to edit.
|
|
|
|
**Common customization requests:**
|
|
- "Change the archetypes to [backend/frontend/data/devops] roles" → edit `modes/_profile.md` or `config/profile.yml`
|
|
- "Translate the modes to English" → edit all files in `modes/`
|
|
- "Add these companies to my portals" → edit `portals.yml`
|
|
- "Update my profile" → edit `config/profile.yml`
|
|
- "Change the CV template design" → edit `templates/cv-template.html`
|
|
- "Adjust the scoring weights" → edit `modes/_profile.md` for user-specific weighting, or edit `modes/_shared.md` and `batch/batch-prompt.md` only when changing the shared system defaults for everyone
|
|
|
|
### Language Modes
|
|
|
|
Default modes are in `modes/` (English). Language-specific modes live in `modes/{lang}/` — each has `_shared.md`, the eval/apply/`pipeline.md` modes, and a `README.md` documenting that market's vocabulary:
|
|
|
|
| Language | Dir | Markets |
|
|
|----------|-----|---------|
|
|
| German | `modes/de/` | DACH (Germany, Austria, Switzerland) |
|
|
| French | `modes/fr/` | France, Belgium, Switzerland, Luxembourg, Quebec |
|
|
| Japanese | `modes/ja/` | Japan |
|
|
|
|
**When to use a `{lang}` mode** — if any holds: the user says "use {lang} modes"; `config/profile.yml` sets `language.modes_dir: modes/{lang}`; or you detect a {lang} JD (then suggest switching). Read from `modes/{lang}/` instead of `modes/`.
|
|
|
|
**When NOT to:** if the user applies to English-language roles — even at French, German, or Japanese companies — use the default English modes.
|
|
|
|
### Skill Modes
|
|
|
|
| If the user... | Mode |
|
|
|----------------|------|
|
|
| Pastes JD or URL | auto-pipeline (evaluate + report + PDF + tracker) |
|
|
| Asks to evaluate offer | `oferta` |
|
|
| Asks to compare offers | `ofertas` |
|
|
| Wants LinkedIn outreach | `contacto` |
|
|
| Wants a formal application email | `email` — draft-only; never sends, submits, or clicks anything |
|
|
| Asks for company research | `deep` |
|
|
| Preps for interview at specific company | `interview-prep` |
|
|
| Wants interactive profile/CV onboarding | `interview` |
|
|
| Wants a time-blocked prep plan for an upcoming interview | `interview/plan` |
|
|
| Wants to run practice interview questions with feedback | `interview/practice` |
|
|
| Wants to debrief after a real interview and close gaps | `interview/debrief` |
|
|
| Wants to generate CV/PDF | `pdf` |
|
|
| Evaluates a course/cert | `training` |
|
|
| Evaluates portfolio project | `project` |
|
|
| Asks about application status | `tracker` |
|
|
| Fills out application form | `apply` |
|
|
| Searches for new offers | `scan` |
|
|
| Processes pending URLs | `pipeline` |
|
|
| Batch processes offers | `batch` |
|
|
| Asks about rejection patterns, wants to improve targeting, or wants to match interview answers to best-fit roles | `patterns` |
|
|
| Asks about follow-ups or application cadence | `followup` |
|
|
|
|
### CV Source of Truth
|
|
|
|
- `cv.md` in project root is the canonical CV
|
|
- `article-digest.md` has detailed proof points (optional)
|
|
- **NEVER hardcode metrics** -- read them from these files at evaluation time
|
|
|
|
---
|
|
|
|
## Ethical Use -- CRITICAL
|
|
|
|
**This system is designed for quality, not quantity.** The goal is to help the user find and apply to roles where there is a genuine match -- not to spam companies with mass applications.
|
|
|
|
- **NEVER submit an application without the user reviewing it first.** Fill forms, draft answers, generate PDFs -- but always STOP before clicking Submit/Send/Apply. The user makes the final call.
|
|
- **Strongly discourage low-fit applications.** If a score is below 4.0/5, explicitly recommend against applying. The user's time and the recruiter's time are both valuable. Only proceed if the user has a specific reason to override the score.
|
|
- **Quality over speed.** A well-targeted application to 5 companies beats a generic blast to 50. Guide the user toward fewer, better applications.
|
|
- **Respect recruiters' time.** Every application a human reads costs someone's attention. Only send what's worth reading.
|
|
|
|
---
|
|
|
|
## Offer Verification -- MANDATORY
|
|
|
|
Verify a posting is still live before applying — using the cheapest check that works (a false "expired" is worse than a slow check: it makes the user miss a real job):
|
|
|
|
1. **ATS-hosted postings (Greenhouse, Lever, ...) — API first, zero tokens:** run `node check-liveness.mjs <url>`. It hits the posting's public ATS JSON API directly (no browser, no tokens) and reports `active`/`expired`, falling back to a browser only when the API is inconclusive. A definitive `expired` from the API is authoritative.
|
|
2. **Non-ATS pages, or when the API is inconclusive — Playwright:** `browser_navigate` to the URL + `browser_snapshot`. Only footer/navbar without JD = closed; title + description + Apply = active.
|
|
|
|
**NEVER decide liveness from a bare WebSearch/WebFetch snippet** — use `check-liveness.mjs` (which does the API rung) or Playwright.
|
|
|
|
**Exception for batch workers (`claude -p`):** Playwright is unavailable in headless pipe mode. The API rung above still works for ATS postings; for non-ATS pages use WebFetch as a fallback and mark the report header `**Verification:** unconfirmed (batch mode)`.
|
|
|
|
---
|
|
|
|
## Stack and Conventions
|
|
|
|
- Node.js (mjs modules), Playwright (PDF + scraping), YAML (config), HTML/CSS (template), Markdown (data), Canva MCP (optional visual CV)
|
|
- Scripts in `.mjs`, configuration in YAML
|
|
- Output in `output/` (gitignored), Reports in `reports/`
|
|
- JDs in `jds/` (referenced as `local:jds/{file}` in pipeline.md)
|
|
- Batch in `batch/` (gitignored except scripts and prompt)
|
|
- Report numbering: sequential 3-digit zero-padded, max existing + 1
|
|
- **RULE: After each batch of evaluations, run `node merge-tracker.mjs`** to merge tracker additions and avoid duplications.
|
|
- **RULE: NEVER create new entries in applications.md if company+role already exists.** Update the existing entry.
|
|
|
|
### TSV Format for Tracker Additions
|
|
|
|
Full column spec (order, format, link normalization) lives in `modes/_shared.md` — read it before writing a tracker-additions TSV.
|
|
|
|
### Pipeline Integrity
|
|
|
|
1. **NEVER edit applications.md to ADD new entries** -- Write TSV in `batch/tracker-additions/` and `merge-tracker.mjs` handles the merge.
|
|
2. **YES you can edit applications.md to UPDATE status/notes of existing entries.**
|
|
3. All reports MUST include `**URL:**` in the header (between Score and PDF). Include `**Legitimacy:** {tier}` (see Block G in `modes/oferta.md`).
|
|
4. All statuses MUST be canonical (see `templates/states.yml`).
|
|
5. Health check: `node verify-pipeline.mjs`
|
|
6. Normalize statuses: `node normalize-statuses.mjs`
|
|
7. Dedup: `node dedup-tracker.mjs`
|
|
|
|
### Canonical States (applications.md)
|
|
|
|
**Source of truth (full descriptions + aliases):** `templates/states.yml`. The 8 canonical states (use exactly one): `Evaluated` · `Applied` · `Responded` · `Interview` · `Offer` · `Rejected` · `Discarded` · `SKIP`.
|
|
|
|
**RULES:** no markdown bold (`**`), no dates (those go in the date column), no extra text (use the notes column) in the status field.
|
|
@AGENTS.md
|
|
<!-- Add anything Claude Code specific that other agents don't need -->
|