pupila: Skill for Claude Code

.claude/skills/pupila-filters/SKILL.md

pupila-filters is a skill for Claude Code from FranRom/pupila. It costs 64 tokens per session (2,846 once invoked), scanned A, original, MIT.

A guide for changing how job listings are scored, filtered out, or explained in a job-matching codebase.

In plain words
What is it for?
Use it to tune job-fit scores, add exclusion rules, change keyword lists, or find out why a particular job was kept or rejected.
Why use it?
It shows where scoring weights, keywords, and hard-exclusion rules live, so you can adjust results without guessing or overlooking related code.

Skill for Claude Code

Written for Claude Code: installed under .claude/.

This is FranRom/pupila's own configuration. It tells Claude Code how to work on pupila itself, so it is not a mod to install elsewhere. Copy it as a starting point and replace the rules that are about this project. Everything pupila configures →

Reuse

Borrowing it

Nothing to install: this file belongs to FranRom/pupila. Take a copy, put it at the same path in your own repository, and replace the rules that are about this project with yours.

Copy the file
curl -O https://raw.githubusercontent.com/FranRom/pupila/main/.claude/skills/pupila-filters/SKILL.md
Clone the repo
git clone --depth 1 https://github.com/FranRom/pupila

Made for: Claude Code.

Wrote this? Show the measurements

A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.

agentmods badge for pupila-filters

README.md
[![agentmods](https://agentmods.dev/badge/skills/franrom/pupila/pupila-filters/github.svg)](https://agentmods.dev/skills/franrom/pupila/pupila-filters)
Your own site
<a href="https://agentmods.dev/skills/franrom/pupila/pupila-filters"><img src="https://agentmods.dev/badge/skills/franrom/pupila/pupila-filters/github.svg" alt="Measured on agentmods" height="20"></a>

Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.

agentmods 80×15 button for pupila-filters

Your own site · 80×15
<a href="https://agentmods.dev/skills/franrom/pupila/pupila-filters"><img src="https://agentmods.dev/badge/skills/franrom/pupila/pupila-filters.svg" alt="Reviewed on agentmods" width="80" height="20"></a>
Per session 64 Skills are progressive disclosure: only the name and description are preloaded; the body loads when the skill is used.
When invoked 2,846 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 0 findings. A grade says what 26 rules found in the file — not that it is safe.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5.1 $0.00064 $0.02846
Opus 5 $0.00032 $0.01423
Sonnet 5 $0.00013 $0.00569
Haiku 4.5 $0.00006 $0.00285

Measured 11d ago against content hash 04751efec425, method: parsed. Prices are Anthropic first-party input rates as of 2026-09-11, from the pricing page.

Security

Grade A, and why

pupila-filters scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 11d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Nothing flagged

None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.

.claude/skills/pupila-filters/SKILL.md · 159 lines

How it starts

The opening of the file, as written. The whole thing — 159 lines — stays where its author put it; the contents beside it link to each section on GitHub.

All filter logic lives in src/filters.ts. Weights + keyword lists load from config/profile.json at runtime via loadProfile() (NOT a static import — the file is gitignored and auto-bootstrapped from config/profile.default.json on first run). Adjusting weights or keywords is a non-code edit to profile.json.

Decision tree: what kind of change are you making?

Task Edit Skill section
Change a scoring weight (e.g. seniority +15 → +20) config/profile.json#weights.<field> "Tuning weights" below
Add/remove a keyword (e.g. another Rust framework) config/profile.json#keywords.<list> "Tuning keywords" below
Add a new hard-drop rule src/filters.ts (HARD_RULES array) "Adding a hard-drop rule" below
Add a new positive signal type src/types.ts + config/profile.json#weights + src/filters.ts "Adding a new positive signal" below
Diagnose why a job has fitScore=N Inspect _signals on the job "Debugging via _signals" below

Order of operations (in applyFilters)

  1. Hard excludes — URL safety, junior/intern titles, location-incompatible (persona-neutral — see "Location & work type" below), non-engineering compounds, leadership, non-frontend eng (since user is a frontend engineer), non-tech roles.
  2. Body preparationpreparedScoringBody() strips boilerplate (EEO, privacy, "About us") and truncates to scoringBodyMaxChars (default 1500). Keyword scoring runs against this — prevents footer text like "we use Anthropic Claude internally" from landing a +20 AI signal on a backend role. Hard-drops still see the full body.
  3. Soft scoring — additive, capped at maxScore (100):
    • Categories — each configured CategoryDef whose keywords match adds its weight once (binary); default weight 0 = pure label. Replaces the old hardcoded web3/ai signals. A job is tagged with every match (Job.categories).
    • Stack (+10 React/Next/TS, +5 RN/Expo, +5 GraphQL/Tailwind/Vite) — tiered
    • Seniority (+15 lead/staff/principal/head, +10 senior/sr) — binary
    • Frontend title (+10) — binary
    • Frontend body (+10) — tiered
    • Location (+10 when the job matches an accepted region / is remote — driven by the location block) — binary
    • Freshness (+10 within 7d, +5 within 14d) — binary
  4. NegativeoutOfRegionPenalty (default -10) if the job is region-locked outside the candidate's accepted regions and they haven't opted into hard-excluding. Applied after capping. (Persona-neutral; replaced the old US-centric penalty.)
  5. Drop — anything with fitScore < minScoreToKeep (default 30).
  6. Categoriesjob.categories = ids of every CategoryDef whose keywords matched (multi-label, config order); [] when none (renders under synthetic "Other"). Defined in config/profile.json#categories, generated from the brief on Regenerate or edited on the Profile tab. Each entry: { id, label, keywords, scope?, weight?, limit? }.

Read the full file on GitHub · 159 lines

Files

What ships with it

1 file beside SKILL.md in the same directory: the scripts, references and assets a skill reads on demand. Not counted in the per-session cost; read them before you install if any of them is executable.

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 11d ago First seen · 159 lines · 64 tokens per session scan A 04751efec425

Subscribe to this mod's changes

pupila-filters is a skill published in the GitHub repository FranRom/pupila (36 stars, last pushed 2mo ago), licensed MIT. It adds 64 tokens to every session and 2,846 once invoked, about $0.0003 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-30.