job-forge 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.codex/config.toml +8 -0
  2. package/.cursor/mcp.json +21 -0
  3. package/.cursor/rules/main.mdc +519 -0
  4. package/.mcp.json +21 -0
  5. package/.opencode/agents/general-free.md +85 -0
  6. package/.opencode/agents/general-paid.md +39 -0
  7. package/.opencode/agents/glm-minimal.md +50 -0
  8. package/.opencode/skills/job-forge.md +185 -0
  9. package/AGENTS.md +514 -0
  10. package/CLAUDE.md +514 -0
  11. package/LICENSE +21 -0
  12. package/README.md +195 -0
  13. package/batch/README.md +60 -0
  14. package/batch/batch-prompt.md +399 -0
  15. package/batch/batch-runner.sh +673 -0
  16. package/bin/create-job-forge.mjs +375 -0
  17. package/bin/job-forge.mjs +120 -0
  18. package/bin/sync.mjs +141 -0
  19. package/config/profile.example.yml +67 -0
  20. package/cv-sync-check.mjs +128 -0
  21. package/dedup-tracker.mjs +201 -0
  22. package/docs/ARCHITECTURE.md +220 -0
  23. package/docs/CUSTOMIZATION.md +101 -0
  24. package/docs/MODEL-ROUTING.md +195 -0
  25. package/docs/README.md +54 -0
  26. package/docs/SETUP.md +186 -0
  27. package/docs/demo.gif +0 -0
  28. package/fonts/dm-sans-latin-ext.woff2 +0 -0
  29. package/fonts/dm-sans-latin.woff2 +0 -0
  30. package/fonts/space-grotesk-latin-ext.woff2 +0 -0
  31. package/fonts/space-grotesk-latin.woff2 +0 -0
  32. package/generate-pdf.mjs +168 -0
  33. package/iso/agents/general-free.md +90 -0
  34. package/iso/agents/general-paid.md +44 -0
  35. package/iso/agents/glm-minimal.md +55 -0
  36. package/iso/commands/job-forge.md +188 -0
  37. package/iso/config.json +7 -0
  38. package/iso/instructions.md +514 -0
  39. package/iso/mcp.json +15 -0
  40. package/merge-tracker.mjs +377 -0
  41. package/modes/README.md +30 -0
  42. package/modes/_shared-calibration.md +26 -0
  43. package/modes/_shared.md +272 -0
  44. package/modes/apply.md +257 -0
  45. package/modes/auto-pipeline.md +70 -0
  46. package/modes/batch.md +110 -0
  47. package/modes/compare.md +23 -0
  48. package/modes/contact.md +82 -0
  49. package/modes/deep.md +99 -0
  50. package/modes/followup.md +68 -0
  51. package/modes/negotiation.md +146 -0
  52. package/modes/offer.md +199 -0
  53. package/modes/pdf.md +121 -0
  54. package/modes/pipeline.md +83 -0
  55. package/modes/project.md +30 -0
  56. package/modes/rejection.md +92 -0
  57. package/modes/scan.md +185 -0
  58. package/modes/tracker.md +31 -0
  59. package/modes/training.md +27 -0
  60. package/normalize-statuses.mjs +152 -0
  61. package/opencode.json +28 -0
  62. package/package.json +78 -0
  63. package/scripts/add-tags.mjs +894 -0
  64. package/scripts/cursor-agent-loop.sh +211 -0
  65. package/scripts/cursor-agent-stream-format.py +134 -0
  66. package/scripts/next-num.mjs +33 -0
  67. package/scripts/release/check-source.mjs +37 -0
  68. package/scripts/render-report-header.mjs +78 -0
  69. package/scripts/session-report.mjs +129 -0
  70. package/scripts/slugify.mjs +27 -0
  71. package/scripts/today.mjs +20 -0
  72. package/scripts/token-usage-report.mjs +315 -0
  73. package/scripts/tracker-line.mjs +67 -0
  74. package/scripts/verify-greenhouse-urls.mjs +195 -0
  75. package/templates/cv-template.html +395 -0
  76. package/templates/portals.example.yml +3140 -0
  77. package/templates/states.yml +62 -0
  78. package/tracker-lib.mjs +257 -0
  79. package/verify-pipeline.mjs +267 -0
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # JobForge
2
+
3
+ > AI-powered job search pipeline built on opencode. Evaluate offers, generate tailored CVs, scan portals, negotiate offers, and track everything -- powered by AI agents.
4
+
5
+ ![opencode](https://img.shields.io/badge/opencode-000?style=flat&logoColor=white)
6
+ ![Node.js](https://img.shields.io/badge/Node.js-339933?style=flat&logo=node.js&logoColor=white)
7
+ ![Geometra](https://img.shields.io/badge/Geometra_MCP-4A90D9?style=flat&logoColor=white)
8
+ ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)
9
+ ![Made in USA](https://img.shields.io/badge/Made_in-USA_%F0%9F%87%BA%F0%9F%87%B8-red?style=flat)
10
+
11
+ <p align="center">
12
+ <img src="demo/demo.gif" alt="JobForge Demo" width="800">
13
+ </p>
14
+
15
+ <p align="center"><em>Paste a job URL. Get a scored evaluation, tailored CV, and tracked application — in seconds.</em></p>
16
+
17
+ ---
18
+
19
+ ## Quick Start
20
+
21
+ ```bash
22
+ npx github:razroo/JobForge create-job-forge my-job-search
23
+ cd my-job-search
24
+ npm install
25
+ opencode
26
+ ```
27
+
28
+ The scaffolded `opencode.json` already has the Geometra MCP (browser automation + PDF) and Gmail MCP (reading replies) wired up — they launch automatically the first time opencode starts.
29
+
30
+ Then fill in `cv.md`, `config/profile.yml`, and `portals.yml` with your personal data, paste a job URL into opencode, and JobForge evaluates + tracks it.
31
+
32
+ **Upgrade later:** `npm run update-harness` (pulls latest harness + fallback plugin, re-syncs symlinks, prints the resolved commit)
33
+
34
+ Full setup guide and alternative install paths (including contributing to the harness itself): **[docs/SETUP.md](docs/SETUP.md)**.
35
+
36
+ ---
37
+
38
+ ## What Is This
39
+
40
+ JobForge turns opencode into a full job search command center. Instead of manually tracking applications in a spreadsheet, you get an AI-powered pipeline that:
41
+
42
+ - **Evaluates offers** with a unified 10-dimension weighted scoring system
43
+ - **Generates tailored PDFs** -- ATS-optimized CVs with anti-AI-detection writing rules
44
+ - **Scans portals** with fuzzy dedup (catches reposts with new URLs)
45
+ - **Processes in batch** -- evaluate 10+ offers in parallel with sub-agents
46
+ - **Tracks everything** with pipeline integrity checks and canonical state management
47
+ - **Manages follow-ups** -- timing-based nudges so you never miss a window
48
+ - **Learns from rejections** -- pattern analysis across all rejections by stage, archetype, and score
49
+ - **Negotiates offers** -- structured comp breakdown, leverage assessment, counter-offer strategy
50
+
51
+ > **Important: This is NOT a spray-and-pray tool.** The whole point is to apply only where there's a real match. The scoring system helps you focus on high-fit opportunities instead of wasting everyone's time. Always review before submitting.
52
+
53
+ ## Features
54
+
55
+ | Feature | Description |
56
+ |---------|-------------|
57
+ | **Auto-Pipeline** | Paste a URL, get a full evaluation + PDF + tracker entry |
58
+ | **Unified Scoring** | 10 weighted dimensions, consistent across all modes, with calibration anchors |
59
+ | **Anti-AI-Detection CVs** | Writing rules that avoid ATS filters on Indeed, LinkedIn, Workday |
60
+ | **6-Block Evaluation** | Role summary, CV match, level strategy, comp research, personalization, interview prep (STAR+R) |
61
+ | **Interview Story Bank** | Curated bank of 10-12 stories with match counts, archetype tags, and automatic pruning |
62
+ | **Follow-Up System** | Timing-based nudges: Applied 7+ days ago nudge, Interviewed 1 day ago thank-you note, email scanning via Gmail MCP |
63
+ | **Gmail Integration** | MCP server configured to retrieve emails for interview callbacks, offer responses, and application status updates |
64
+ | **Rejection Analysis** | Captures stage + reason, surfaces patterns (archetype gaps, scoring miscalibration) |
65
+ | **Offer Negotiation** | Total comp breakdown, equity valuation, leverage from pipeline, counter-offer scripts |
66
+ | **Deep Research** | Company research that feeds back into scores and interview prep |
67
+ | **Smart LinkedIn Outreach** | Reads evaluation reports to craft targeted messages using top proof points |
68
+ | **Portal Scanner** | 45+ companies pre-configured with fuzzy dedup for reposts |
69
+ | **Batch Processing** | Parallel evaluation with `opencode run` workers, with honest verification flagging |
70
+ | **Pipeline Integrity** | Automated merge, dedup, status normalization, health checks |
71
+ | **Cost-Aware Agent Routing** | Three subagents (`@general-free`, `@general-paid`, `@glm-minimal`) with per-task model tiers; procedural work runs on free-tier models, quality-sensitive work on paid. See [Subagent Routing in AGENTS.md](AGENTS.md) for the task-to-agent mapping. |
72
+ | **Automatic Model Fallback** | When a model rate-limits or 5xx's, [`@razroo/opencode-model-fallback`](https://www.npmjs.com/package/@razroo/opencode-model-fallback) rotates the agent through a configured `fallback_models` chain and replays the request. Ships with sensible defaults: free-tier agents fall back to another free model then to paid as an escape hatch, paid agents fall back to a different paid provider. |
73
+ | **Token Cost Visibility** | `job-forge tokens --days 1` for per-session breakdown; `job-forge session-report --since-minutes 60 --log` to flag sessions over budget and append history to `data/token-usage.tsv`. Auto-logged after every batch run. |
74
+
75
+ ## Usage
76
+
77
+ ```
78
+ /job-forge → Show all available commands
79
+ /job-forge {paste a JD} → Full auto-pipeline (evaluate + PDF + tracker)
80
+ /job-forge scan → Scan portals for new offers
81
+ /job-forge pdf → Generate ATS-optimized CV
82
+ /job-forge batch → Batch evaluate multiple offers
83
+ /job-forge tracker → View application status
84
+ /job-forge apply → Fill application forms with AI
85
+ /job-forge pipeline → Process pending URLs
86
+ /job-forge contact → LinkedIn outreach (uses evaluation report)
87
+ /job-forge deep → Deep company research (feeds back into scores)
88
+ /job-forge followup → Check what needs follow-up action
89
+ /job-forge rejection → Record/analyze rejection patterns
90
+ /job-forge negotiation → Structured offer negotiation
91
+ /job-forge training → Evaluate a course/cert
92
+ /job-forge project → Evaluate a portfolio project
93
+ ```
94
+
95
+ Or just paste a job URL or description directly -- JobForge auto-detects it and runs the full pipeline.
96
+
97
+ > **The system is designed to be customized by opencode itself.** Modes, archetypes, scoring weights, negotiation scripts -- just ask opencode to change them: "Change the archetypes to backend engineering roles", "Add these 5 companies to portals.yml", "Update my profile with this CV I'm pasting".
98
+
99
+ ## How It Works
100
+
101
+ ```
102
+ You paste a job URL or description
103
+
104
+
105
+ ┌──────────────────┐
106
+ │ Archetype │ Classifies: LLMOps / Agentic / PM / SA / FDE / Transformation
107
+ │ Detection │
108
+ └────────┬─────────┘
109
+
110
+ ┌────────▼─────────┐
111
+ │ A-F Evaluation │ Match, gaps, comp research, STAR stories
112
+ │ (reads cv.md) │ Unified 10-dimension scoring model
113
+ └────────┬─────────┘
114
+
115
+ ┌────┼────┐
116
+ ▼ ▼ ▼
117
+ Report PDF Tracker
118
+ .md .pdf .tsv
119
+
120
+ ┌────┼────┐
121
+ ▼ ▼ ▼
122
+ Apply Follow Negotiate
123
+ up (if offer)
124
+ ```
125
+
126
+ ## Project Structure
127
+
128
+ **Your personal project** (after `npx create-job-forge my-search`):
129
+
130
+ ```
131
+ my-search/
132
+ ├── package.json # depends on "job-forge" (github:razroo/JobForge)
133
+ ├── opencode.json # thin config — enables MCPs + states.yml
134
+ ├── cv.md # your CV (personal)
135
+ ├── article-digest.md # your proof points (optional, personal)
136
+ ├── portals.yml # companies you want to scan (personal)
137
+ ├── config/profile.yml # your identity, target roles (personal)
138
+ ├── data/ # applications, pipeline, scan history (personal, gitignored)
139
+ ├── reports/ # generated evaluation reports (personal, gitignored)
140
+ ├── batch/
141
+ │ ├── batch-input.tsv # URLs to batch-evaluate (personal)
142
+ │ ├── batch-state.tsv # resumable batch state (personal)
143
+ │ ├── tracker-additions/ # TSVs waiting to merge (personal)
144
+ │ ├── logs/ # per-worker logs (personal, gitignored)
145
+ │ ├── batch-prompt.md # → symlink to node_modules/job-forge/
146
+ │ └── batch-runner.sh # → symlink to node_modules/job-forge/
147
+ ├── modes/ # → symlink to node_modules/job-forge/modes/
148
+ ├── templates/ # → symlink to node_modules/job-forge/templates/
149
+ ├── .opencode/skills/job-forge.md # → symlink to node_modules/job-forge/
150
+ └── node_modules/job-forge/ # the harness (fetched from github:razroo/JobForge)
151
+ ```
152
+
153
+ Symlinks are regenerated on every `npm install` via the package's `postinstall` hook. You never have to know about harness internals — just edit `cv.md`, `portals.yml`, and `config/profile.yml`.
154
+
155
+ **The harness itself** (this repo, what gets installed into `node_modules/job-forge/`):
156
+
157
+ ```
158
+ JobForge/
159
+ ├── package.json # bin: job-forge, create-job-forge
160
+ ├── bin/
161
+ │ ├── job-forge.mjs # CLI dispatcher (merge/verify/pdf/tokens/sync/...)
162
+ │ ├── sync.mjs # postinstall: creates symlinks in consumer project
163
+ │ └── create-job-forge.mjs # npx create-job-forge scaffolder
164
+ ├── .opencode/skills/job-forge.md # the skill router
165
+ ├── modes/ # _shared.md + 16 skill modes
166
+ ├── templates/ # cv-template.html, portals.example.yml, states.yml
167
+ ├── config/profile.example.yml # template for consumer's profile.yml
168
+ ├── batch/batch-prompt.md # batch worker prompt template
169
+ ├── batch/batch-runner.sh # parallel orchestrator
170
+ ├── scripts/token-usage-report.mjs # opencode cost analyzer
171
+ ├── tracker-lib.mjs # shared tracker read/write helper
172
+ ├── merge-tracker.mjs # merge batch TSVs → tracker
173
+ ├── dedup-tracker.mjs # remove dupes
174
+ ├── verify-pipeline.mjs # pipeline integrity checks
175
+ ├── normalize-statuses.mjs # canonicalize status values
176
+ ├── generate-pdf.mjs # CV PDF generator
177
+ ├── cv-sync-check.mjs # setup lint
178
+ ├── dashboard/ # optional Go TUI
179
+ ├── fonts/ # Space Grotesk + DM Sans (for PDF)
180
+ └── docs/ # architecture, setup, customization
181
+ ```
182
+
183
+ ## Documentation
184
+
185
+ Index and cross-links: [docs/README.md](docs/README.md).
186
+
187
+ - [Setup](docs/SETUP.md) — both install paths, profile, CV, portals, verify, token tracking, troubleshooting
188
+ - [Architecture](docs/ARCHITECTURE.md) — package architecture, modes, evaluation flow, batch runner, pipeline scripts
189
+ - [Customization](docs/CUSTOMIZATION.md) — archetypes, scanner keywords, CV template, states, customizing symlinked modes
190
+ - [Model Routing](docs/MODEL-ROUTING.md) — the three cost-tiered subagents, why the architecture exists, and how to swap models or add your own
191
+ - [Contributing](CONTRIBUTING.md) — branch workflow, quality gate, and ideas for PRs
192
+
193
+ ## License
194
+
195
+ MIT
@@ -0,0 +1,60 @@
1
+ # Batch evaluation
2
+
3
+ The `batch/` folder holds the **parallel batch runner** for processing 10+ job URLs with headless `opencode run` workers. For how batch fits into the rest of JobForge, see [docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md).
4
+
5
+ ## What ships in git
6
+
7
+ | Path | Role |
8
+ |------|------|
9
+ | `batch-runner.sh` | Orchestrator: parallelism, state, retries, resume |
10
+ | `batch-prompt.md` | Prompt template passed to each worker (keep evaluation and scoring instructions aligned with the canonical model in [`modes/_shared.md`](../modes/_shared.md) so batch scores match single-offer runs) |
11
+ | `README.md` | This file |
12
+
13
+ ## Local-only files (gitignored when present)
14
+
15
+ Per [`.gitignore`](../.gitignore): `batch-input.tsv`, `batch-state.tsv`, `logs/*`, and `tracker-additions/*.tsv`. Empty dirs (`logs/`, `tracker-additions/`) use `.gitkeep` so the tree exists in a fresh clone.
16
+
17
+ ## Input: `batch-input.tsv`
18
+
19
+ Tab-separated UTF-8 text, with a **header row** (the runner skips the literal `id` header):
20
+
21
+ | Column | Required | Description |
22
+ |--------|----------|-------------|
23
+ | `id` | Yes | Numeric offer id (used for state and ordering) |
24
+ | `url` | Yes | Job posting URL |
25
+ | `source` | No | Short label (e.g. board or company) |
26
+ | `notes` | No | Free text for your own context |
27
+
28
+ Example:
29
+
30
+ ```text
31
+ id url source notes
32
+ 1 https://jobs.example.com/123 greenhouse Staff engineer
33
+ 2 https://boards.example.com/456 ashby Remote OK
34
+ ```
35
+
36
+ Create this file in **`batch/`** next to the runner (see `batch-runner.sh` constants). Then:
37
+
38
+ ```bash
39
+ ./batch/batch-runner.sh --dry-run # from repo root
40
+ ./batch/batch-runner.sh
41
+ ```
42
+
43
+ Options and file layout: `./batch/batch-runner.sh --help`.
44
+
45
+ ## After a batch run
46
+
47
+ Workers write one-line TSV rows under `batch/tracker-additions/`. Merge them into your tracker from the repo root:
48
+
49
+ ```bash
50
+ npm run merge
51
+ npm run verify # optional: pipeline health after merge (report links, statuses, pending TSVs)
52
+ ```
53
+
54
+ (`node merge-tracker.mjs` — same as `npm run merge`; see [CONTRIBUTING.md](../CONTRIBUTING.md#development).)
55
+
56
+ After a successful merge, each processed file is moved to **`batch/tracker-additions/merged/`** (created on first merge when the directory does not yet exist). `npm run verify` only looks for `*.tsv` files in the **top level** of `batch/tracker-additions/`, so rows already merged and archived under `merged/` do not trigger the “pending TSVs” warning.
57
+
58
+ ## Prerequisites
59
+
60
+ The runner expects the `opencode` CLI on `PATH` and a valid `batch-prompt.md`. It creates `reports/` and tracker paths when they do not exist; ensure your usual JobForge setup (`cv.md`, `config/profile.yml`, `portals.yml`) matches what `batch-prompt.md` assumes.
@@ -0,0 +1,399 @@
1
+ # job-forge Batch Worker — Full Evaluation + PDF + Tracker Line
2
+
3
+ You are a job offer evaluation worker for the candidate (read name from config/profile.yml). You may receive either a **single offer** or a **bundle of offers** (the orchestrator chooses based on `--bundle-size`). For each offer you produce:
4
+
5
+ 1. Full A-F evaluation (report .md)
6
+ 2. Personalized ATS-optimized PDF
7
+ 3. Tracker line for later merge
8
+
9
+ ## Bundled mode (when the user message contains a JSON array of offers)
10
+
11
+ If the user message carries an `Offers: [ ... ]` JSON array, process each offer **fully and sequentially** — do not interleave steps across offers. For each offer in order:
12
+
13
+ 1. Run the full pipeline below (Steps 1-6) using its `id`, `url`, `jd_file`, `report_num`, `date`.
14
+ 2. After finishing that offer, emit **exactly one single-line JSON status** to stdout with this shape:
15
+ ```
16
+ {"id":"<id>","status":"completed|failed","report_num":"<num>","company":"...","role":"...","score":<num-or-null>,"pdf":"<path-or-null>","report":"<path-or-null>","error":"<msg-or-null>"}
17
+ ```
18
+ 3. Move to the next offer. **Do NOT stop the bundle if one offer fails** — mark it failed and continue.
19
+ 4. The orchestrator (`batch-runner.sh`) parses these status lines to update `batch-state.tsv`. Missing status = worker didn't reach that offer = counts as failed.
20
+
21
+ ## Single-offer mode (legacy, when no Offers array)
22
+
23
+ Read `{{URL}}`, `{{JD_FILE}}`, `{{REPORT_NUM}}`, `{{DATE}}`, `{{ID}}` from the user message and run the pipeline once. Emit the same single-line status JSON at the end.
24
+
25
+ **IMPORTANT**: This prompt is self-contained. You have EVERYTHING you need here. You do not depend on any other skill or system.
26
+
27
+ ---
28
+
29
+ ## Sources of Truth (READ before evaluating)
30
+
31
+ | File | Absolute path | When |
32
+ |------|---------------|------|
33
+ | cv.md | `cv.md (project root)` | ALWAYS |
34
+ | llms.txt | `llms.txt (if exists)` | ALWAYS |
35
+ | article-digest.md | `article-digest.md (project root)` | ALWAYS (proof points) |
36
+ | i18n.ts | `i18n.ts (if exists, optional)` | Only for interviews/deep |
37
+ | cv-template.html | `templates/cv-template.html` | For PDF |
38
+ | generate-pdf.mjs | `generate-pdf.mjs` | For PDF |
39
+
40
+ **RULE: NEVER write to cv.md or i18n.ts.** They are read-only.
41
+ **RULE: NEVER hardcode metrics.** Read them from cv.md + article-digest.md at evaluation time.
42
+ **RULE: For article metrics, article-digest.md takes precedence over cv.md.** cv.md may have older numbers — that's normal.
43
+
44
+ ---
45
+
46
+ ## Job-Specific Parameters (read from user message)
47
+
48
+ The orchestrator passes the concrete values for this job **in the user message**, not in this prompt. This prompt is a static template shared across all workers so the opencode prompt cache can be reused — resolving per-job values here would bust the cache on every run.
49
+
50
+ Look in the user message for:
51
+
52
+ | Parameter | Description |
53
+ |-----------|-------------|
54
+ | URL | Offer URL |
55
+ | JD file | Path to the file containing the JD text |
56
+ | Report number | 3 digits, zero-padded (001, 002...) |
57
+ | Date | YYYY-MM-DD |
58
+ | Batch ID | Unique offer ID from batch-input.tsv |
59
+
60
+ Everywhere this prompt writes `{{URL}}`, `{{JD_FILE}}`, `{{REPORT_NUM}}`, `{{DATE}}`, `{{ID}}`, substitute the values from the user message.
61
+
62
+ ---
63
+
64
+ ## Pipeline (execute in order)
65
+
66
+ ### Step 1 — Retrieve JD
67
+
68
+ **Note: Batch workers do NOT have Geometra MCP/browser access.** Use WebFetch only, and fetch each URL at most once.
69
+
70
+ 1. Read the JD file at `{{JD_FILE}}` (provided by orchestrator)
71
+ 2. If the JD file has content, use it directly — do NOT also WebFetch the URL (the file is the source of truth)
72
+ 3. If the JD file is empty or missing, WebFetch `{{URL}}` **once**
73
+ 4. If WebFetch returns content but it looks like a shell page (no JD text, just navbar/footer), add `**Verification: unconfirmed**` to the report header and proceed — the conductor or user will verify later
74
+ 5. If both the file and WebFetch fail, report an error and stop
75
+
76
+ **Never chain WebFetch + file read + second WebFetch for the same URL** — each fetch re-pulls the same tokens into context.
77
+
78
+ ### Step 2 — A-F Evaluation
79
+
80
+ `cv.md` is already in your context via `opencode.json:instructions` — use it directly, do NOT Read it again. Execute ALL blocks:
81
+
82
+ #### Step 0 — Archetype Detection
83
+
84
+ Classify the offer into one of the 6 archetypes. If it's a hybrid, indicate the 2 closest ones.
85
+
86
+ **The 6 archetypes (all equally valid):**
87
+
88
+ | Archetype | Key themes | What they're buying |
89
+ |-----------|------------|---------------------|
90
+ | **AI Platform / LLMOps Engineer** | Evaluation, observability, reliability, pipelines | Someone who puts AI into production with metrics |
91
+ | **Agentic Workflows / Automation** | HITL, tooling, orchestration, multi-agent | Someone who builds reliable agent systems |
92
+ | **Technical AI Product Manager** | GenAI/Agents, PRDs, discovery, delivery | Someone who translates business → AI product |
93
+ | **AI Solutions Architect** | Hyperautomation, enterprise, integrations | Someone who designs end-to-end AI architectures |
94
+ | **AI Forward Deployed Engineer** | Client-facing, fast delivery, prototyping | Someone who delivers AI solutions to clients fast |
95
+ | **AI Transformation Lead** | Change management, adoption, org enablement | Someone who leads AI transformation in an organization |
96
+
97
+ **Adaptive framing:**
98
+
99
+ > **Concrete metrics are read from `cv.md` + `article-digest.md` at each evaluation. NEVER hardcode numbers here.**
100
+
101
+ | If the role is... | Emphasize about the candidate... | Proof point sources |
102
+ |-------------------|--------------------------|--------------------------|
103
+ | Platform / LLMOps | Production systems builder, observability, evals, closed-loop | article-digest.md + cv.md |
104
+ | Agentic / Automation | Multi-agent orchestration, HITL, reliability, cost | article-digest.md + cv.md |
105
+ | Technical AI PM | Product discovery, PRDs, metrics, stakeholder mgmt | cv.md + article-digest.md |
106
+ | Solutions Architect | System design, integrations, enterprise-ready | article-digest.md + cv.md |
107
+ | Forward Deployed Engineer | Fast delivery, client-facing, prototype → prod | cv.md + article-digest.md |
108
+ | AI Transformation Lead | Change management, team enablement, adoption | cv.md + article-digest.md |
109
+
110
+ **Cross-cutting advantage**: Frame the candidate's profile as a **"Technical builder"** who adapts their framing to the role.
111
+
112
+ - For PM: "builder who reduces uncertainty with prototypes and then productionizes with discipline".
113
+ - For FDE: "builder who delivers fast with observability and metrics from day 1".
114
+ - For SA: "builder who designs end-to-end systems with real integration experience".
115
+ - For LLMOps: "builder who puts AI into production with closed-loop quality systems — read metrics from article-digest.md".
116
+
117
+ Turn "builder" into a professional signal, not a "hobby maker" label. The framing changes, the truth stays the same.
118
+
119
+ #### Block A — Role Summary
120
+
121
+ Table with: Detected archetype, Domain, Function, Seniority, Remote, Team size, TL;DR.
122
+
123
+ #### Block B — CV Match
124
+
125
+ `cv.md` is already in context (via `instructions`) — use it directly, do NOT Read it again. Table with each JD requirement mapped to exact CV lines or i18n.ts keys.
126
+
127
+ **Adapted to archetype:**
128
+
129
+ - FDE → prioritize fast delivery and client-facing.
130
+ - SA → prioritize system design and integrations.
131
+ - PM → prioritize product discovery and metrics.
132
+ - LLMOps → prioritize evals, observability, pipelines.
133
+ - Agentic → prioritize multi-agent, HITL, orchestration.
134
+ - Transformation → prioritize change management, adoption, scaling.
135
+
136
+ **Gaps** section with mitigation strategy for each one:
137
+ 1. Is it a hard blocker or a preference-only requirement?
138
+ 2. Can the candidate demonstrate adjacent experience?
139
+ 3. Is there a portfolio project that covers this gap?
140
+ 4. Concrete mitigation plan
141
+
142
+ #### Block C — Level and Strategy
143
+
144
+ 1. **Detected level** in the JD vs **candidate's current level** (staff / senior / mid, based on title history in cv.md)
145
+ 2. **"Sell senior without lying" plan**: specific phrases, concrete achievements, founder experience as an advantage
146
+ 3. **"If I get downleveled" plan**: accept if comp is fair, 6-month review, clear criteria
147
+
148
+ #### Block D — Comp and Demand
149
+
150
+ Use WebSearch for current salaries (Glassdoor, Levels.fyi, Blind), company comp reputation, demand trends. Table with data and cited sources. If no data available, say so.
151
+
152
+ Comp score (1-5): 5=top quartile, 4=above market, 3=median, 2=slightly below, 1=well below.
153
+
154
+ #### Block E — Personalization Plan
155
+
156
+ | # | Section | Current state | Proposed change | Why |
157
+ |---|---------|---------------|-----------------|-----|
158
+
159
+ Top 5 CV changes + Top 5 LinkedIn changes.
160
+
161
+ #### Block F — Interview Plan
162
+
163
+ 6-10 STAR stories mapped to JD requirements:
164
+
165
+ | # | JD Requirement | STAR Story | S | T | A | R |
166
+
167
+ **Selection adapted to archetype.** Also include:
168
+ - 1 recommended case study (which project to present and how)
169
+ - Red-flag questions and how to answer them
170
+
171
+ #### Global Score
172
+
173
+ **Use the Canonical Scoring Model from `modes/_shared.md`.** All 10 dimensions, weighted exactly as defined there. This ensures scores from batch workers are directly comparable to scores from interactive evaluations and the `compare` comparison mode.
174
+
175
+ **Emit the score as a single JSON block** per `_shared.md` → "Score Emission — EMIT-ONCE JSON". Do NOT also write a prose scoring table — the JSON is the only representation. The report `.md` embeds this JSON verbatim under a `## Score` section, and Blocks A-F reference it by key instead of re-enumerating the 10 dimensions.
176
+
177
+ ### Step 3 — Save Report .md
178
+
179
+ Save the full evaluation to:
180
+ ```
181
+ reports/{{REPORT_NUM}}-{company-slug}-{{DATE}}.md
182
+ ```
183
+
184
+ Where `{company-slug}` is the company name in lowercase, no spaces, with hyphens.
185
+
186
+ **Report format:**
187
+
188
+ ```markdown
189
+ # Evaluation: {Company} — {Role}
190
+
191
+ **Date:** {{DATE}}
192
+ **Archetype:** {detected}
193
+ **Score:** {X.X/5}
194
+ **URL:** {original offer URL}
195
+ **PDF:** job-forge/output/cv-candidate-{company-slug}-{{DATE}}.pdf
196
+ **Batch ID:** {{ID}}
197
+
198
+ ---
199
+
200
+ ## Score
201
+
202
+ {the Score JSON block from _shared.md, verbatim, inside a fenced ```json block}
203
+
204
+ ---
205
+
206
+ ## A) Role Summary
207
+ (full content — reference scores by key, do not re-enumerate the 10 dimensions)
208
+
209
+ ## B) CV Match
210
+ (full content)
211
+
212
+ ## C) Level and Strategy
213
+ (full content)
214
+
215
+ ## D) Comp and Demand
216
+ (full content)
217
+
218
+ ## E) Personalization Plan
219
+ (full content)
220
+
221
+ ## F) Interview Plan
222
+ (full content)
223
+
224
+ ---
225
+
226
+ ## Extracted Keywords
227
+ (15-20 JD keywords for ATS)
228
+ ```
229
+
230
+ ### Step 4 — Generate PDF
231
+
232
+ 1. Read `cv.md` + `i18n.ts`
233
+ 2. Extract 15-20 keywords from the JD
234
+ 3. Detect JD language → CV language (EN default)
235
+ 4. Detect company location → paper format: US/Canada → `letter`, rest → `a4`
236
+ 5. Detect archetype → adapt framing
237
+ 6. Rewrite Professional Summary injecting keywords
238
+ 7. Select top 3-4 most relevant projects
239
+ 8. Reorder experience bullets by relevance to JD
240
+ 9. Build competency grid (6-8 keyword phrases)
241
+ 10. Inject keywords into existing achievements (**NEVER fabricate**)
242
+ 11. Generate full HTML from template (read `templates/cv-template.html`)
243
+ 12. Write HTML to `/tmp/cv-candidate-{company-slug}.html`
244
+ 13. Run:
245
+ ```bash
246
+ node generate-pdf.mjs \
247
+ /tmp/cv-candidate-{company-slug}.html \
248
+ output/cv-candidate-{company-slug}-{{DATE}}.pdf \
249
+ --format={letter|a4}
250
+ ```
251
+ 14. Report: PDF path, page count, keyword coverage %
252
+
253
+ **ATS rules:**
254
+ - Single-column (no sidebars)
255
+ - Standard headers: "Professional Summary", "Work Experience", "Education", "Skills", "Certifications", "Projects"
256
+ - No text in images/SVGs
257
+ - No critical info in headers/footers
258
+ - UTF-8, selectable text
259
+ - Keywords distributed: Summary (top 5), first bullet of each role, Skills section
260
+
261
+ **Design:**
262
+ - Fonts: Space Grotesk (headings, 600-700) + DM Sans (body, 400-500)
263
+ - Fonts self-hosted: `fonts/`
264
+ - Header: Space Grotesk 24px bold + cyan→purple 2px gradient + contact info
265
+ - Section headers: Space Grotesk 13px uppercase, cyan color `hsl(187,74%,32%)`
266
+ - Body: DM Sans 11px, line-height 1.5
267
+ - Company names: purple `hsl(270,70%,45%)`
268
+ - Margins: 0.6in
269
+ - Background: white
270
+
271
+ **Keyword injection strategy (ethical):**
272
+ - Reformulate real experience using the exact vocabulary from the JD
273
+ - NEVER add skills the candidate doesn't have
274
+ - Example: JD says "RAG pipelines" and CV says "LLM workflows with retrieval" → "RAG pipeline design and LLM orchestration workflows"
275
+
276
+ **Writing style — Anti-AI-detection (CRITICAL):**
277
+ ATS platforms (Indeed, LinkedIn, Workday) flag AI-generated CVs. All generated text MUST read as human-written:
278
+ - **Vary sentence length.** Mix short fragments with longer sentences. Don't make every bullet the same length.
279
+ - **Start bullets differently.** Bullets MUST NOT all begin with a past-tense action verb — mix in noun-led and metric-led openings.
280
+ - **Use the candidate's own phrasing from cv.md when possible.** Reformulate for keywords, but preserve their voice.
281
+ - **NEVER use these AI-hallmark words:** "leveraged", "utilized", "spearheaded", "orchestrated" (as metaphor), "cutting-edge", "passionate about", "drive innovation", "synergy", "holistic approach", "navigate complex", "foster collaboration".
282
+ - **Use plain, specific verbs.** "Built" not "architected". "Ran" not "orchestrated". "Fixed" not "remediated".
283
+ - **Don't over-polish.** Real CVs have minor asymmetries — one job has 4 bullets, another has 3. Don't normalize everything.
284
+ - **Self-check before generating HTML:** (1) Do 3+ bullets start with same word? Fix. (2) Are all bullets same length? Vary. (3) Any AI-hallmark words? Rewrite.
285
+
286
+ **Template placeholders (in cv-template.html):**
287
+
288
+ | Placeholder | Content |
289
+ |-------------|---------|
290
+ | `{{LANG}}` | `en` or `es` |
291
+ | `{{PAGE_WIDTH}}` | `8.5in` (letter) or `210mm` (A4) |
292
+ | `{{NAME}}` | (from profile.yml) |
293
+ | `{{EMAIL}}` | (from profile.yml) |
294
+ | `{{LINKEDIN_URL}}` | (from profile.yml) |
295
+ | `{{LINKEDIN_DISPLAY}}` | (from profile.yml) |
296
+ | `{{PORTFOLIO_URL}}` | (from profile.yml) |
297
+ | `{{PORTFOLIO_DISPLAY}}` | (from profile.yml) |
298
+ | `{{LOCATION}}` | (from profile.yml) |
299
+ | `{{SECTION_SUMMARY}}` | Professional Summary / Resumen Profesional |
300
+ | `{{SUMMARY_TEXT}}` | Personalized summary with keywords |
301
+ | `{{SECTION_COMPETENCIES}}` | Core Competencies / Competencias Core |
302
+ | `{{COMPETENCIES}}` | `<span class="competency-tag">keyword</span>` × 6-8 |
303
+ | `{{SECTION_EXPERIENCE}}` | Work Experience / Experiencia Laboral |
304
+ | `{{EXPERIENCE}}` | HTML for each job with reordered bullets |
305
+ | `{{SECTION_PROJECTS}}` | Projects / Proyectos |
306
+ | `{{PROJECTS}}` | HTML for top 3-4 projects |
307
+ | `{{SECTION_EDUCATION}}` | Education / Formacion |
308
+ | `{{EDUCATION}}` | HTML for education |
309
+ | `{{SECTION_CERTIFICATIONS}}` | Certifications / Certificaciones |
310
+ | `{{CERTIFICATIONS}}` | HTML for certifications |
311
+ | `{{SECTION_SKILLS}}` | Skills / Competencias |
312
+ | `{{SKILLS}}` | HTML for skills |
313
+
314
+ ### Step 5 — Tracker Line
315
+
316
+ Write a single TSV line to:
317
+ ```
318
+ batch/tracker-additions/{{ID}}.tsv
319
+ ```
320
+
321
+ TSV format (single line, no header, 9 tab-separated columns):
322
+ ```
323
+ {next_num}\t{{DATE}}\t{company}\t{role}\t{status}\t{score}/5\t{pdf_emoji}\t[{{REPORT_NUM}}](reports/{{REPORT_NUM}}-{company-slug}-{{DATE}}.md)\t{one_line_note}
324
+ ```
325
+
326
+ **TSV columns (exact order):**
327
+
328
+ | # | Field | Type | Example | Validation |
329
+ |---|-------|------|---------|------------|
330
+ | 1 | num | int | `647` | Sequential, max existing + 1 |
331
+ | 2 | date | YYYY-MM-DD | `2026-03-14` | Evaluation date |
332
+ | 3 | company | string | `Datadog` | Short company name |
333
+ | 4 | role | string | `Staff AI Engineer` | Role title |
334
+ | 5 | status | canonical | `Evaluated` | MUST be canonical (see states.yml) |
335
+ | 6 | score | X.XX/5 | `4.55/5` | Or `N/A` if not scorable |
336
+ | 7 | pdf | emoji | `✅` or `❌` | Whether PDF was generated |
337
+ | 8 | report | md link | `[647](reports/647-...)` | Link to report |
338
+ | 9 | notes | string | `APPLY HIGH...` | 1-sentence summary |
339
+
340
+ **IMPORTANT:** The TSV order has status BEFORE score (col 5→status, col 6→score). In the tracker day files the order is reversed (col 5→score, col 6→status). merge-tracker.mjs handles the conversion.
341
+
342
+ **Valid canonical states:** `Evaluated`, `Applied`, `Responded`, `Interview`, `Offer`, `Rejected`, `Discarded`, `SKIP`
343
+
344
+ Where `{next_num}` is calculated by reading all entries across day files in `data/applications/` and taking the max entry number.
345
+
346
+ ### Step 6 — Final Output
347
+
348
+ When finished, print a JSON summary to stdout for the orchestrator to parse (template uses `{{ID}}` / `{company}` placeholders — substitute before emitting):
349
+
350
+ ```
351
+ {
352
+ "status": "completed",
353
+ "id": "{{ID}}",
354
+ "report_num": "{{REPORT_NUM}}",
355
+ "company": "{company}",
356
+ "role": "{role}",
357
+ "score": {score_num},
358
+ "pdf": "{pdf_path}",
359
+ "report": "{report_path}",
360
+ "error": null
361
+ }
362
+ ```
363
+
364
+ If something fails:
365
+ ```
366
+ {
367
+ "status": "failed",
368
+ "id": "{{ID}}",
369
+ "report_num": "{{REPORT_NUM}}",
370
+ "company": "{company_or_unknown}",
371
+ "role": "{role_or_unknown}",
372
+ "score": null,
373
+ "pdf": null,
374
+ "report": "{report_path_if_exists}",
375
+ "error": "{error_description}"
376
+ }
377
+ ```
378
+
379
+ ---
380
+
381
+ ## Global Rules
382
+
383
+ ### NEVER
384
+ 1. Fabricate experience or metrics
385
+ 2. Modify cv.md, i18n.ts, or portfolio files
386
+ 3. Share the phone number in generated messages
387
+ 4. Recommend comp below market rate
388
+ 5. Generate a PDF without reading the JD first
389
+ 6. Use corporate-speak
390
+ 7. Use AI-hallmark words: "leveraged", "utilized", "spearheaded", "orchestrated" (as metaphor), "cutting-edge", "passionate about", "drive innovation", "synergy", "holistic approach". ATS platforms flag these.
391
+
392
+ ### ALWAYS
393
+ 1. Read cv.md, llms.txt, and article-digest.md before evaluating
394
+ 2. Detect the role's archetype and adapt the framing
395
+ 3. Cite exact CV lines when there's a match
396
+ 4. Use WebSearch for comp and company data
397
+ 5. Generate content in the JD's language (EN default)
398
+ 6. Be direct and actionable — no fluff
399
+ 7. When generating English text (PDF summaries, bullets, STAR stories), use native tech English: short sentences, action verbs, no unnecessary passive voice, no "in order to" or "utilized"