Files
santifer--career-ops/ARCHITECTURE.md
T
wehub-resource-sync d083df1fdb
CodeQL Analysis / Analyze (javascript-typescript) (push) Failing after 2s
Web CI / web typecheck + build (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
CodeQL Analysis / Analyze (go) (push) Failing after 16s
chore: import upstream snapshot with attribution
2026-07-13 12:02:43 +08:00

6.9 KiB
Raw Blame History

Architecture

A high-level map of how career-ops is put together. For the precise system/user file boundary, see DATA_CONTRACT.md; for contribution mechanics, see CONTRIBUTING.md.

Principles

Career-ops is built on three commitments that every design decision serves:

  • Local-first. Everything runs on your machine against your files. No account required, no server in the loop for the core tool.
  • AI-agnostic. The logic lives in Markdown prompt files under modes/, executed by whatever AI coding CLI you use (Claude Code, Codex, OpenCode, Gemini, Qwen, Grok, Antigravity) or by standalone Node scripts. No single model is hardcoded.
  • Human-in-the-loop. The tool prepares and evaluates; the human reviews and clicks. It never submits applications on your behalf.

The two layers (the data contract)

The single most important architectural rule: system files and user files are strictly separated.

  • System layer — the tool itself: modes/, scripts (*.mjs), templates, the dashboard. These are versioned and updated by update-system.mjs. Listed in SYSTEM_PATHS.
  • User layer — your data: cv.md, config/profile.yml, modes/_profile.md, data/, reports/, jds/, etc. The updater never touches these. Listed in USER_PATHS.

DATA_CONTRACT.md is the source of truth for this boundary, and updater-migration-tests.mjs enforces that no system path ever overlaps a user path.

Files are canonical — databases are derived

Settled doctrine (#918): the human-readable, git-diffable files (data/applications.md, reports/, data/pipeline.md) are the permanent source of truth. SQLite exists only as a derived index (fast queries, reindex-on-delete) and will never become a primary store — not even opt-in. The reason is ecosystem-wide: the web UI, the Go dashboard, community plugins, and thousands of fork scripts all read the files; a second canonical store would force every reader to support two modes forever. Performance work is welcome on the derived layer; the files stay the brain.

Why the flat root

The repo keeps its ~50 scripts at the root deliberately (#1386). Path stability is a feature here, not an accident: the updater's SYSTEM_PATHS allowlist, community plugins, docs, guides, and the muscle memory of thousands of users (node scan.mjs) all reference these paths. A cosmetic reorganization would break forks and plugins for no functional gain. The conventions that keep the flat root navigable: one script = one job, *.test.mjs sits next to what it tests, and every script is registered in SYSTEM_PATHS (enforced in CI by the coverage guard).

Component map

AI coding CLI  ─┐
(or scripts)    │  reads prompt files
                ▼
   modes/*.md  ──────────────►  the "brain": scoring, evaluation,
   (_shared.md = scoring core)   apply, scan, interview, etc. prompts
                │
   ┌────────────┼─────────────────────────────────────────────┐
   ▼            ▼                  ▼               ▼            ▼
 scan        evaluate          generate         track       update
 scan.mjs    oferta.md         PDFs/CVs/        data/        update-
 providers/  (+eval scripts)   cover letters    reports/     system.mjs

Discovery — scan.mjs + providers/

Finds jobs from open, no-auth public sources. scan.mjs is zero-token: it calls public ATS APIs (Greenhouse, Ashby, Lever, BambooHR, Teamtailor, Workday, Breezy) and RSS/JSON boards via per-board modules in providers/. Auth-gated/login-required sources are intentionally out of core (they belong in the plugin layer). Results land in data/pipeline.md.

Evaluation — modes/oferta.md + modes/_shared.md

The heart of the tool. oferta.md defines the AG evaluation blocks; _shared.md defines the 15 scoring system, archetype detection, posting-legitimacy signals, and global rules. The AI reads these plus your cv.md and produces a structured report.

Standalone evaluators let you run the same scoring without an interactive CLI, against cheaper/local models: gemini-eval.mjs (Google free tier), ollama-eval.mjs (fully local), and openai-eval.mjs (any OpenAI-compatible endpoint).

Generation — PDFs, CVs, cover letters

generate-pdf.mjs (Playwright HTML→PDF), generate-latex.mjs / build-cv-latex.mjs, generate-cover-letter.mjs. ATS-safe templates live in templates/ and fonts/.

Tracking — data/ + reports/ + tracker scripts

Every evaluated offer is registered. data/applications.md is the canonical tracker table; reports/{NNN}-{company}-{date}.md holds full evaluations. tracker.mjs, merge-tracker.mjs, dedup-tracker.mjs, normalize-statuses.mjs, and reconcile-pipeline.mjs keep it consistent (atomic writes + a SQLite index). Report numbers are claimed atomically via reserve-report-num.mjs.

Liveness — never evaluate a dead posting

check-liveness.mjs / liveness-*.mjs verify a posting is still open (zero-token) before it costs evaluation time.

Self-update — update-system.mjs

Safely pulls new system files from upstream without touching user data. It backs up, fetches, re-execs the target updater (resolving its import closure so a new import can't break the upgrade), then checks out only SYSTEM_PATHS. BOOTSTRAP_PATHS covers very old installs.

Multi-CLI entry files

Each CLI reads its own entry file, all of which point at the canonical AGENTS.md: CLAUDE.md (full), and thin @AGENTS.md redirect wrappers OPENCODE.md, CODEX.md, GEMINI.md, plus the .agents/skills/ skill entrypoints. This is the open agent skill standard.

Dashboard (optional)

A standalone Go TUI under dashboard/ for browsing the pipeline. Isolated from the core — never required.

Data flow (a typical run)

scan ──► data/pipeline.md ──► evaluate (oferta + cv) ──► reports/NNN-*.md
                                          │                      │
                                          └──► data/applications.md (tracker)
                                                         │
                                          apply (human reviews + clicks)

Quality gates

  • test-all.mjs — the full suite (500+ checks across scoring, scan, tracker, PDF, security, updater).
  • updater-migration-tests.mjs — enforces the system/user boundary and safe cross-version upgrades.
  • CI: test + CodeQL are required; CodeRabbit reviews every PR; Renovate keeps deps current.

Where to start reading

  • The boundary → DATA_CONTRACT.md
  • The scoring → modes/_shared.md + modes/oferta.md
  • Adding a job source → an existing module in providers/ (mirror it)
  • The updater → update-system.mjs