Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 0 additions & 95 deletions .claude/skills/career-ops/SKILL.md

This file was deleted.

1 change: 1 addition & 0 deletions .claude/skills/career-ops/SKILL.md
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: santifer
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ jobs:
node-version: '20'
- uses: actions/setup-go@v6
with:
go-version: '1.22'
go-version: '1.26'
- run: npm install
- run: node test-all.mjs --quick
342 changes: 335 additions & 7 deletions AGENTS.md

Large diffs are not rendered by default.

370 changes: 2 additions & 368 deletions CLAUDE.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion DATA_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ These files contain your personal data, customizations, and work product. Update
| `data/pipeline.md` | Your URL inbox |
| `data/scan-history.tsv` | Your scan history |
| `data/follow-ups.md` | Your follow-up history |
| `writing-samples/*` | Your personal writing samples for style calibration |
| `writing-samples/*` | Your personal writing samples for style calibration (except `writing-samples/README.md`, which is system-owned documentation delivered by updates) |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Do not reclassify a previously user-layer path into system-layer ownership.

This change makes writing-samples/README.md system-owned while the parent writing-samples/* path is user data. That weakens the contract and can permit updater writes inside a user-layer directory.

As per coding guidelines: DATA_CONTRACT.md: “This file defines system vs user file boundaries. Changes here are critical — reject if user-layer files are being reclassified.”

Also applies to: 65-65

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DATA_CONTRACT.md` at line 21, The change reclassifies a file under a
user-owned directory by marking `writing-samples/README.md` as system-owned
while `writing-samples/*` remains user-owned; revert that by keeping ownership
consistent: either make `writing-samples/README.md` user-owned (keep
`writing-samples/*` as-is) or move the system-owned README into a true system
directory, and update the DATA_CONTRACT entry for `writing-samples/*` and
`writing-samples/README.md` so parent and child paths have consistent ownership.

| `reports/*` | Your evaluation reports |
| `output/*` | Your generated PDFs |
| `jds/*` | Your saved job descriptions |
Expand Down Expand Up @@ -62,6 +62,7 @@ These files contain system logic, scripts, templates, and instructions that impr
| `docs/*` | Documentation |
| `VERSION` | Current version number |
| `DATA_CONTRACT.md` | This file |
| `writing-samples/README.md` | System-owned onboarding documentation for the writing-samples directory |

## The Rule

Expand Down
120 changes: 2 additions & 118 deletions GEMINI.md
Original file line number Diff line number Diff line change
@@ -1,118 +1,2 @@
# Career-Ops — AI Job Search Pipeline (Gemini CLI)

> This file is auto-loaded by the Gemini CLI as persistent context.
> It is the Gemini equivalent of CLAUDE.md.
> All slash commands are defined in `.gemini/commands/`.

## What is career-ops

AI-powered job search automation: pipeline tracking, offer evaluation, CV generation, portal scanning, batch processing. Originally built on Claude Code, now fully supported on Gemini CLI and OpenCode.

## Data Contract (CRITICAL)

**User Layer (NEVER auto-updated — your personalizations live here):**
- `cv.md`, `config/profile.yml`, `modes/_profile.md`, `article-digest.md`, `portals.yml`
- `data/*`, `reports/*`, `output/*`, `interview-prep/*`

**System Layer (auto-updatable — do NOT put user data here):**
- `modes/_shared.md`, `modes/oferta.md`, all other modes
- `GEMINI.md`, `CLAUDE.md`, `*.mjs` scripts, `templates/*`, `batch/*`

**THE RULE:** When the user asks to customize anything (archetypes, narrative, negotiation scripts, proof points, location policy, comp targets), ALWAYS write to `modes/_profile.md` or `config/profile.yml`. NEVER edit `modes/_shared.md` for user-specific content.

## 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", ...}` → tell the user an update is available and ask if they want to apply it (`node update-system.mjs apply`)
- `{"status": "up-to-date"}` → say nothing
- `{"status": "dismissed"}` or `{"status": "offline"}` → say nothing

## Gemini CLI Commands

When using [Gemini CLI](https://github.com/google-gemini/gemini-cli), the following slash commands are available (defined in `.gemini/commands/`):

| Command | Claude Code Equivalent | Description |
|---------|------------------------|-------------|
| `/career-ops` | `/career-ops` | Show menu or evaluate JD |
| `/career-ops-pipeline` | `/career-ops pipeline` | Process pending URLs from inbox |
| `/career-ops-evaluate` | `/career-ops oferta` | Evaluate job offer (A-G scoring) |
| `/career-ops-compare` | `/career-ops ofertas` | Compare and rank multiple offers |
| `/career-ops-contact` | `/career-ops contacto` | LinkedIn outreach |
| `/career-ops-deep` | `/career-ops deep` | Deep company research |
| `/career-ops-pdf` | `/career-ops pdf` | Generate ATS-optimized CV |
| `/career-ops-training` | `/career-ops training` | Evaluate course/cert |
| `/career-ops-project` | `/career-ops project` | Evaluate portfolio project |
| `/career-ops-tracker` | `/career-ops tracker` | Application status overview |
| `/career-ops-apply` | `/career-ops apply` | Live application assistant |
| `/career-ops-scan` | `/career-ops scan` | Scan portals for new offers |
| `/career-ops-batch` | `/career-ops batch` | Batch processing |
| `/career-ops-patterns` | `/career-ops patterns` | Analyze rejection patterns |
| `/career-ops-followup` | `/career-ops followup` | Follow-up cadence tracker |

**All commands share the same evaluation logic** in `modes/*.md`. The `modes/` files are shared between Claude Code, OpenCode, and Gemini CLI.

## First Run — Onboarding

**Before doing anything else, check if the system is set up.** Run silently every session:

1. Does `cv.md` exist?
2. Does `config/profile.yml` exist (not just profile.example.yml)?
3. Does `modes/_profile.md` exist (not just _profile.template.md)?
4. Does `portals.yml` exist (not just templates/portals.example.yml)?

If `modes/_profile.md` is missing, copy from `modes/_profile.template.md` silently.

**If ANY of these is missing, enter onboarding mode.** Guide the user step by step — ask for their CV, fill the profile, set up the tracker. See `CLAUDE.md` for the full onboarding script (identical logic applies here).

## Skill Modes

| If the user... | Mode to load |
|----------------|-------------|
| Pastes JD or URL | auto-pipeline → read `modes/_shared.md` + `modes/auto-pipeline.md` |
| Asks to evaluate offer | read `modes/_shared.md` + `modes/oferta.md` |
| Asks to compare offers | read `modes/_shared.md` + `modes/ofertas.md` |
| Wants LinkedIn outreach | read `modes/_shared.md` + `modes/contacto.md` |
| Asks for company research | read `modes/deep.md` |
| Preps for interview | read `modes/interview-prep.md` |
| Wants to generate CV/PDF | read `modes/_shared.md` + `modes/pdf.md` |
| Evaluates a course/cert | read `modes/training.md` |
| Evaluates portfolio project | read `modes/project.md` |
| Asks about application status | read `modes/tracker.md` |
| Fills out application form | read `modes/_shared.md` + `modes/apply.md` |
| Searches for new offers | read `modes/_shared.md` + `modes/scan.md` |
| Processes pending URLs | read `modes/_shared.md` + `modes/pipeline.md` |
| Batch processes offers | read `modes/_shared.md` + `modes/batch.md` |
| Asks about rejection patterns | read `modes/patterns.md` |
| Asks about follow-ups | read `modes/followup.md` |

## Main Files

| File | Function |
|------|----------|
| `data/applications.md` | Application tracker |
| `data/pipeline.md` | Inbox of pending URLs |
| `portals.yml` | Query and company config |
| `templates/cv-template.html` | HTML template for CVs |
| `generate-pdf.mjs` | Playwright: HTML to PDF |
| `article-digest.md` | Proof points from portfolio (optional) |
| `interview-prep/story-bank.md` | Accumulated STAR+R stories |
| `gemini-eval.mjs` | Standalone Gemini API evaluator (no CLI required) |

## Ethical Use — CRITICAL

- **NEVER submit an application without the user reviewing it first.** Fill forms, draft answers, generate PDFs — but always STOP before clicking Submit. The user makes the final call.
- **Strongly discourage low-fit applications.** If a score is below 4.0/5, explicitly recommend against applying.
- **Quality over speed.** A well-targeted application to 5 companies beats a generic blast to 50.

## Pipeline Integrity

1. **NEVER edit applications.md to ADD new entries** — Write TSV in `batch/tracker-additions/` and `node merge-tracker.mjs` handles the merge.
2. Run `node verify-pipeline.mjs` to check health.
3. All reports MUST include `**URL:**` and `**Legitimacy:**` in the header.
4. All statuses MUST be canonical (see `templates/states.yml`).
@./AGENTS.md
<!-- Add anything Gemini specific that other agents don't need -->
Comment on lines +1 to +2
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a top-level heading to satisfy markdown linting.

Line 1 currently starts with an include directive, which triggers MD041 (first-line-heading). Add an H1 before the include so docs lint stays clean.

💡 Suggested patch
+# Gemini
+
 @./AGENTS.md
 <!-- Add anything Gemini specific that other agents don't need -->
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@./AGENTS.md
<!-- Add anything Gemini specific that other agents don't need -->
# Gemini
@./AGENTS.md
<!-- Add anything Gemini specific that other agents don't need -->
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 1-1: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@GEMINI.md` around lines 1 - 2, Add a top-level H1 heading at the start of
GEMINI.md to satisfy MD041 (first-line-heading) before the existing include
directive "@./AGENTS.md"; update the file so the first line is a Markdown H1
(e.g., "# Gemini") followed by the existing include line to keep the include
intact and resolve the lint error.

5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ node gemini-eval.mjs --file ./jds/my-job.txt
npm run gemini:eval -- "JD text here"
```

> **Free tier:** Both options work without billing. Native CLI uses Google OAuth; the API script uses `gemini-2.0-flash` (15 RPM, 1M tokens/day free).
> **Free tier:** Both options work without billing. Native CLI uses Google OAuth; the API script uses `gemini-2.5-flash` (15 RPM, 1M tokens/day free).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What are the current free-tier limits and default free model for Gemini API and Gemini CLI, according to official Google documentation?

💡 Result:

As of May 17, 2026, the free-tier limits and model access for the Gemini API and Gemini CLI are structured as follows: Gemini API Free Tier The Gemini API free tier is designed for developers and small projects [1][2]. It provides free access to input and output tokens, but it is subject to specific rate limits and model restrictions [1][2]. - Model Access: The free tier is restricted to Flash models (e.g., Gemini 2.5 Flash and Flash-Lite) [3]. Access to Pro models typically requires a paid API key or a subscription [3]. - Rate Limits: Limits are project-based and can be viewed in Google AI Studio [4][5]. While specific limits vary by model, they are generally lower than those in paid tiers [4][5][6]. - Data Usage: Content submitted via the free tier may be used by Google to improve its products [1][2]. Gemini CLI Free Tier The Gemini CLI offers a free tier that depends on the authentication method used [7][8]. - Authentication via Google Account (Gemini Code Assist for individuals): This method allows for 1,000 model requests per user per day and 60 model requests per user per minute [7][9][8]. - Authentication via Gemini API Key (Unpaid): This method is limited to 250 model requests per user per day and 10 model requests per user per minute, with access restricted to Flash models only [7][8]. - Vertex AI (Express Mode): This offers a trial period (typically 90 days) before billing must be enabled, with variable quotas and model access specific to the account [7][8]. Note: Usage limits and model availability are subject to change [10]. Developers are encouraged to check the official Google AI Studio and Gemini API documentation for the most current information [4][5][10].

Citations:


Consider removing or qualifying hardcoded Gemini quotas in README.

Line 160 specifies 15 RPM and 1M tokens/day, but official Google documentation indicates these limits are "project-based," "vary by model," and "subject to change." These values are not clearly confirmed in the official pricing or rate-limits pages. Either link to the official Gemini API rate limits and Gemini CLI quota page instead, or replace specific numbers with a note like "Free tier limits apply—see official docs for current quotas."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 160, Update the README entry that currently reads "**Free
tier:** Both options work without billing. Native CLI uses Google OAuth; the API
script uses `gemini-2.5-flash` (15 RPM, 1M tokens/day free)." to avoid hardcoded
Gemini quotas: either remove the specific numbers or replace them with a
qualifier like "Free tier limits apply—see official docs for current quotas" and
add links to the official Gemini rate limits and pricing pages (e.g.,
ai.google.dev/gemini-api/docs/rate-limits and
ai.google.dev/gemini-api/docs/pricing or the Gemini CLI quota page) so readers
are pointed to authoritative, up-to-date quota information.


## Usage

Expand Down Expand Up @@ -233,7 +233,8 @@ Features: 6 filter tabs, 4 sort modes, grouped/flat view, lazy-loaded previews,

```
career-ops/
├── CLAUDE.md # Agent instructions
├── AGENTS.md # Canonical agent instructions (all CLIs)
├── CLAUDE.md # Claude Code wrapper (imports AGENTS.md)
├── cv.md # Your CV (create this)
├── article-digest.md # Your proof points (optional)
├── config/
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.6.0
1.8.0
16 changes: 16 additions & 0 deletions batch/batch-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Eres un worker de evaluación de ofertas de empleo for the candidate (read name
| Archivo | Ruta absoluta | Cuándo |
|---------|---------------|--------|
| cv.md | `cv.md (project root)` | SIEMPRE |
| _profile.md | `modes/_profile.md (if exists)` | SIEMPRE (user customizations: archetypes, role_shape, location policy, comp targets) |
| profile.yml | `config/profile.yml (if exists)` | SIEMPRE (candidate identity, comp range, role_shape rules) |
| llms.txt | `llms.txt (if exists)` | SIEMPRE |
| article-digest.md | `article-digest.md (project root)` | SIEMPRE (proof points) |
| i18n.ts | `i18n.ts (if exists, optional)` | Solo entrevistas/deep |
Expand All @@ -24,6 +26,20 @@ Eres un worker de evaluación de ofertas de empleo for the candidate (read name
**REGLA: NUNCA escribir en cv.md ni i18n.ts.** Son read-only.
**REGLA: NUNCA hardcodear métricas.** Leerlas de cv.md + article-digest.md en el momento.
**REGLA: Para métricas de artículos, article-digest.md prevalece sobre cv.md.** cv.md puede tener números más antiguos — es normal.
**REGLA: Antes de evaluar, cargar `modes/_profile.md` y `config/profile.yml` si existen.** Contienen las preferencias del candidato Y reglas concretas de scoring que **sobrescriben** los defaults del sistema.

Tipos de patrones que estos archivos pueden incluir:
- **Caps de bloque** — ej: "cap Block A at 3.0/5 if title contains 'Lead'/'Head'/'Principal'"
- **Overrides de recomendación** — ej: "force SKIP if comp ceiling below $120K" o "force SKIP if role_shape signals broad ownership"
- **Scoring por dimensión** — ej: "Remote: full credit on remote-first; score 2.0 on full on-site outside [region]"
- **Framing adaptativo por archetype** — mappings entre arquetipos detectados y proof points a priorizar

Aplicación durante la evaluación A-G:
- **Bloque A:** aplicar caps de role-shape ANTES de calcular el score del bloque
- **Bloques B-D:** aplicar adaptive framing por archetype y reglas de dimension scoring (location, comp, etc.)
- **Bloque F:** aplicar recommendation overrides (SKIP forzado, etc.) — `_profile.md` puede convertir un score técnicamente alto en un SKIP por shape o por comp

**En conflicto, las reglas de `_profile.md` ganan sobre los defaults de `_shared.md`.** Esto es intencional: `_profile.md` es la capa de personalización del usuario.

---

Expand Down
26 changes: 20 additions & 6 deletions batch/batch-runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ set -euo pipefail
# career-ops batch runner — standalone orchestrator for claude -p workers
# Reads batch-input.tsv, delegates each offer to a claude -p worker,
# tracks state in batch-state.tsv for resumability.
#
# NOTE: This script is Claude Code-specific. It uses claude -p with
# --dangerously-skip-permissions and --append-system-prompt-file flags
# that are not available in other CLIs. Multi-CLI support is out of scope
# for now — contributions welcome.

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
Expand All @@ -28,6 +33,7 @@ RETRY_FAILED=false
START_FROM=0
MAX_RETRIES=2
MIN_SCORE=0
MODEL="" # empty = let claude -p use the Claude Max default

usage() {
cat <<'USAGE'
Expand All @@ -43,6 +49,9 @@ Options:
--start-from N Start from offer ID N (skip earlier IDs)
--max-retries N Max retry attempts per offer (default: 2)
--min-score N Skip PDF/tracker for offers scoring below N (default: 0 = off)
--model NAME Claude model passed to `claude -p --model` (default:
unset = Claude Max default). Use a cheaper model for
large batches, e.g. `--model claude-sonnet-4-6`.
-h, --help Show this help

Files:
Expand Down Expand Up @@ -76,6 +85,7 @@ while [[ $# -gt 0 ]]; do
--start-from) START_FROM="$2"; shift 2 ;;
--max-retries) MAX_RETRIES="$2"; shift 2 ;;
--min-score) MIN_SCORE="$2"; shift 2 ;;
--model) MODEL="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1"; usage; exit 1 ;;
esac
Expand Down Expand Up @@ -350,13 +360,17 @@ process_offer() {
-e "s|{{ID}}|${esc_id}|g" \
"$PROMPT_FILE" > "$resolved_prompt"

# Launch claude -p worker (uses default model from Claude Max subscription)
# Launch claude -p worker.
# Model defaults to the Claude Max subscription default unless --model was
# passed. Building the command in an array keeps quoting safe regardless.
local -a claude_args=(-p --dangerously-skip-permissions)
if [[ -n "$MODEL" ]]; then
claude_args+=(--model "$MODEL")
fi
claude_args+=(--append-system-prompt-file "$resolved_prompt" "$prompt")

local exit_code=0
claude -p \
--dangerously-skip-permissions \
--append-system-prompt-file "$resolved_prompt" \
"$prompt" \
> "$log_file" 2>&1 || exit_code=$?
claude "${claude_args[@]}" > "$log_file" 2>&1 || exit_code=$?

# Cleanup resolved prompt
rm -f "$resolved_prompt"
Expand Down
2 changes: 1 addition & 1 deletion dashboard/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ go 1.24.2
require (
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/x/ansi v0.11.5
github.com/muesli/termenv v0.16.0
)

require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect
github.com/charmbracelet/x/ansi v0.11.5 // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.9.0 // indirect
Expand Down
Loading