A pipeline tracker for a real job search. Ingests job descriptions, scores fit against a profile, drafts follow-ups for routine cases only, and surfaces stale opportunities. The eval-gated LLM stages ship in Phase B.
Part of the ai-portfolio index. Phase A (foundations) is complete; Phase B (evals + first LLM stage) is next. See CHANGELOG.md for the slice-by-slice arc.
Three ideas being demonstrated at once:
- Eval-driven agent design. Every LLM stage is behind an eval suite. The extract / score split (D9) exists so extraction facts and scoring judgment can regress independently.
- Structural safety for dual-use data. The tool runs against real personal data and against a synthetic fixture. Safety is enforced by construction, not by user discipline — two isolated DBs stamped with a mode marker, and two egress points that share one definition of "personal": a pre-commit scanner guarding git, and an authenticated, redacting sanitizer guarding every LLM call (D7, D8). Redaction is unconditional and runs before the payload is authenticated, so no caller can opt out of it — including the ones that forget to. The guarantee's scope is stated narrowly and honestly in D8: structured identifiers and known names, not free-text NER.
- Knowing when not to automate. The drafter routes to a briefing card, not a prose draft, for anything non-routine (D10).
uv sync
uv run python -m jscc validate-config
uv run python -m jscc db init
uv run python -m jscc seed --random-seed 42 --now 2026-08-28T12:00:00+00:00
uv run python -m jscc reportDefault mode is synthetic. Switch by env: JSCC_DATA=real. The real DB (data/real.db) is gitignored; the synthetic one is tracked as a portfolio-visible fixture.
uv run python -m jscc report after the seed above:
[mode: synthetic]
Funnel
------
identified 6
applied 8
recruiter_screen 3
hm_screen 3
technical_loop 2
onsite 1
offer 0
closed 2
(total) 25
Stale alerts (13)
----------------
hm_screen Yield Model Co Director of Engineering, ML overdue by 25d (last interaction 32d ago, threshold 7d)
applied Rift Cloud Director of Engineering, ML overdue by 19d (last interaction 33d ago, threshold 14d)
identified Timber Motors Director of Engineering, ML overdue by 13d (last interaction 20d ago, threshold 7d)
applied Pinnacle Search Director of Engineering, ML overdue by 7d (last interaction 21d ago, threshold 14d)
...
Bit-reproducible for a pinned --random-seed and --now.
jscc/ library code
config.py load + validate stages.yaml, profile.yaml
mode.py synthetic/real mode resolution + DB path convention
storage.py SQLite persistence with stamped mode marker
models.py pydantic domain models (Application, Contact, Interaction, ...)
seed.py deterministic synthetic fixture (evaluation infrastructure)
sanitizer.py the LLM-egress choke point; redacts, then HMAC-wraps
personal_data.py one definition of "personal" — shared by the scanner + sanitizer
instrumentation.py @instrumented — cost/latency/token capture on every LLM call
extraction.py the extract_jd interface (D9 step 1) + JD extraction prompt v1
llm_client.py Anthropic client + StubExtractionClient fallback (no key configured yet)
evals.py hand-rolled eval harness (jd_extraction suite so far)
fetcher.py requests + readability JD fetcher; optional Playwright fallback for JS-heavy pages
report.py staleness detector + funnel counts
cli.py click entry point (ingest, dlq list, resolve-dlq, ...)
tests/ pytest suite (249 tests)
config/ stages.yaml, profile.example.yaml, pipeline.yaml (playwright_fallback flag)
evals/ eval suites (jd_extraction so far); evals/README.md
scripts/ pre-commit content scanner (imports its rules from jscc/personal_data.py); smoke_fetch.py (real-URL smoke test, not CI-gated)
decisions/ ADRs (see below)
docs/ design-principles.md; smoke-test-results.md (smoke_fetch.py output snapshot)
.github/ CI workflow
data/ synthetic.db (tracked); real.db (gitignored)
Design decisions with rejected alternatives:
- ADR-001 — pydantic vs. jsonschema
- ADR-002 — stdlib sqlite3
- ADR-003 — mode isolation via stamped marker
- ADR-004 — pre-commit.com framework + local Python hook
- ADR-005 — sanitizer authenticity via HMAC wrapper
The ten locked design principles behind them are in docs/design-principles.md.
uv sync
uv run pytest # ~seconds
uv run pre-commit install # enable the safety scanner
uv run playwright install chromium # optional -- only needed to use the Playwright fetch fallbackThe pre-commit scanner refuses commits that match name/email/phone patterns or entries in a local .safety/danger-list.local.txt (gitignored).
Phase A hardening complete: three rounds of adversarial + reviewer-walkthrough gates, structural fixes for every critical + high finding, 5 ADRs. Phase B in flight: B1 (eval suite), B2a (extraction prompt + client plumbing), B3a (baseline fetcher + DLQ core), B3b (Playwright fallback + real-URL smoke test), and B4 (JD paste-only path) shipped. No ANTHROPIC_API_KEY is configured yet, so extraction runs against a stub client end-to-end — live prompt iteration to the ≥80% eval bar (B2b) is next once a key is available. 203 pytest cases.
MIT — see LICENSE.