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
@@ -0,0 +1,272 @@
1
+ # Shared Context -- job-forge
2
+
3
+ <!-- ============================================================
4
+ HOW TO CUSTOMIZE THIS FILE
5
+ ============================================================
6
+ This file contains the shared context for all job-forge modes.
7
+ Before using job-forge, you MUST:
8
+ 1. Fill in config/profile.yml with your personal data
9
+ 2. Create your cv.md in the project root
10
+ 3. (Optional) Create article-digest.md with your proof points
11
+ 4. Customize the sections below marked with [CUSTOMIZE]
12
+ ============================================================ -->
13
+
14
+ ## Read these sources of truth
15
+
16
+ | File | Path | Loaded where? |
17
+ |------|------|----------------|
18
+ | cv.md | `cv.md` (project root) | **Already in context** via `opencode.json:instructions` — do NOT Read again |
19
+ | _shared.md | `modes/_shared.md` (this file) | **Already in context** via `instructions` |
20
+ | AGENTS.harness.md | symlink to `node_modules/job-forge/AGENTS.md` | **Already in context** via `instructions` |
21
+ | article-digest.md | `article-digest.md` (if exists) | **Read on demand** — optional detailed proof points, only when evaluating |
22
+ | profile.yml | `config/profile.yml` | **Read on demand** — for name, contact, targets |
23
+ | templates/states.yml | canonical application states | **Already in context** via `instructions` |
24
+
25
+ **RULE: Do NOT Read `cv.md` or `modes/_shared.md` from within a mode — they're already in your context.** Re-Reading them pulls the same tokens into conversation history at full input cost; opencode's `instructions` array caches them for you.
26
+
27
+ **RULE: NEVER hardcode metrics from proof points.** Reference them from the already-loaded cv.md, or Read article-digest.md if you need the richer proof points.
28
+
29
+ **RULE: For article/project metrics, article-digest.md takes precedence over cv.md** (cv.md may have older numbers).
30
+
31
+ ---
32
+
33
+ ## List the North Star target roles
34
+
35
+ The skill applies with EQUAL rigor to ALL target roles. None is primary or secondary -- any is a success if comp and growth are right:
36
+
37
+ | Archetype | Thematic axes | What they buy |
38
+ |-----------|---------------|---------------|
39
+ | **AI Platform / LLMOps Engineer** | Evaluation, observability, reliability, pipelines | Someone who puts AI in production with metrics |
40
+ | **Agentic Workflows / Automation** | HITL, tooling, orchestration, multi-agent | Someone who builds reliable agent systems |
41
+ | **Technical AI Product Manager** | GenAI/Agents, PRDs, discovery, delivery | Someone who translates business to AI product |
42
+ | **AI Solutions Architect** | Hyperautomation, enterprise, integrations | Someone who designs end-to-end AI architectures |
43
+ | **AI Forward Deployed Engineer** | Client-facing, fast delivery, prototyping | Someone who delivers AI solutions to clients fast |
44
+ | **AI Transformation Lead** | Change management, adoption, org enablement | Someone who leads AI transformation in an org |
45
+
46
+ <!-- [CUSTOMIZE] Edit the archetypes above to match YOUR target roles.
47
+ For example, if you're a backend engineer, replace with:
48
+ - Senior Backend Engineer
49
+ - Staff Platform Engineer
50
+ - Engineering Manager
51
+ etc. -->
52
+
53
+ ### Apply framing by archetype
54
+
55
+ > **Concrete metrics: read from `cv.md` + `article-digest.md` at evaluation time. NEVER hardcode numbers here.**
56
+
57
+ | If the role is... | Emphasize about the candidate... | Proof point sources |
58
+ |-------------------|----------------------------------|---------------------|
59
+ | Platform / LLMOps | Production systems builder, observability, evals, closed-loop | article-digest.md + cv.md |
60
+ | Agentic / Automation | Multi-agent orchestration, HITL, reliability, cost | article-digest.md + cv.md |
61
+ | Technical AI PM | Product discovery, PRDs, metrics, stakeholder mgmt | cv.md + article-digest.md |
62
+ | Solutions Architect | System design, integrations, enterprise-ready | article-digest.md + cv.md |
63
+ | Forward Deployed Engineer | Fast delivery, client-facing, prototype to prod | cv.md + article-digest.md |
64
+ | AI Transformation Lead | Change management, team enablement, adoption | cv.md + article-digest.md |
65
+
66
+ <!-- [CUSTOMIZE] Map YOUR specific projects/articles to each archetype above -->
67
+
68
+ ### Use this exit narrative in ALL framings
69
+
70
+ <!-- [CUSTOMIZE] Replace with YOUR narrative. Examples:
71
+ - "Built and sold my SaaS after 5 years. Now focused on applied AI at scale."
72
+ - "Led engineering at a Series B startup through 10x growth. Now seeking my next challenge."
73
+ - "Transitioned from consulting to building product. Looking for high-ownership roles."
74
+ Read from config/profile.yml → narrative.exit_story -->
75
+
76
+ Use the candidate's exit story from `config/profile.yml` to frame ALL content:
77
+ - **In PDF Summaries:** Bridge from past to future -- "Now applying the same [skill] to [JD domain]."
78
+ - **In STAR stories:** Reference proof points from article-digest.md
79
+ - **In Draft Answers (Section G):** Place the transition narrative in the first response.
80
+ - **When the JD asks for "entrepreneurial", "ownership", "builder", "end-to-end":** This is the #1 differentiator. Increase match weight.
81
+
82
+ ### Use the cross-cutting advantage frame
83
+
84
+ Frame the candidate as a **"Technical builder with real-world proof"**. Adapt the phrasing per archetype.
85
+
86
+ - For PM: "builder who reduces uncertainty with prototypes then productionizes with discipline."
87
+ - For FDE: "builder who delivers fast with observability and metrics from day 1."
88
+ - For SA: "builder who designs end-to-end systems with real integration experience."
89
+ - For LLMOps: "builder who puts AI in production with closed-loop quality systems."
90
+
91
+ Convert "builder" into a professional signal, not a "hobby maker". Real proof points make this credible.
92
+
93
+ ### Use the portfolio as a proof point in high-value applications
94
+
95
+ <!-- [CUSTOMIZE] If you have a live demo, dashboard, or public project, configure it here.
96
+ Example:
97
+ dashboard:
98
+ url: "https://yoursite.dev/demo"
99
+ password: "demo-2026"
100
+ when_to_share: "LLMOps, AI Platform, observability roles"
101
+ Read from config/profile.yml → narrative.proof_points and narrative.dashboard -->
102
+
103
+ If the candidate has a live demo/dashboard (check profile.yml), offer access in applications for relevant roles.
104
+
105
+ ### Comp Intelligence
106
+
107
+ <!-- [CUSTOMIZE] Research comp ranges for YOUR target roles and update these ranges -->
108
+
109
+ **Follow this general guidance.**
110
+
111
+ - Use WebSearch for current market data (Glassdoor, Levels.fyi, Blind).
112
+ - Frame by role title, not by skills — titles determine comp bands.
113
+ - Contractor rates are typically 30-50% higher than employee base to account for benefits.
114
+ - Geographic arbitrage works for remote roles: lower CoL = better net.
115
+
116
+ ### Negotiation Scripts
117
+
118
+ <!-- [CUSTOMIZE] Adapt these to your situation -->
119
+
120
+ **Salary expectations (general framework):**
121
+ > "Based on market data for this role, I'm targeting [RANGE from profile.yml]. I'm flexible on structure -- what matters is the total package and the opportunity."
122
+
123
+ **Geographic discount pushback:**
124
+ > "The roles I'm competitive for are output-based, not location-based. My track record doesn't change based on postal code."
125
+
126
+ **When offered below target:**
127
+ > "I'm comparing with opportunities in the [higher range]. I'm drawn to [company] because of [reason]. Can we explore [target]?"
128
+
129
+ ### Location Policy
130
+
131
+ <!-- [CUSTOMIZE] Adapt to your situation. Read from config/profile.yml → location -->
132
+
133
+ **In forms:**
134
+ - Binary "can you be on-site?" questions: follow your actual availability from profile.yml
135
+ - In free-text fields: specify your timezone overlap and availability
136
+
137
+ **In evaluations (scoring):**
138
+ - Remote dimension for hybrid outside your country: score **3.0** (not 1.0)
139
+ - Only score 1.0 if JD explicitly says "must be on-site 4-5 days/week, no exceptions"
140
+
141
+ ### Use time-to-offer as the priority
142
+ - Working demo + metrics > perfection
143
+ - Apply sooner > learn more
144
+ - 80/20 approach, timebox everything
145
+
146
+ ---
147
+
148
+ ## Use this canonical scoring model (SINGLE SOURCE OF TRUTH)
149
+
150
+ **ALL evaluation modes MUST use this exact model.** Whether the offer is evaluated via `offer`, `auto-pipeline`, `batch`, or compared via `compare`, the score is computed the same way. This ensures scores are comparable across the entire pipeline.
151
+
152
+ | # | Dimension | Weight | 1 | 3 | 5 |
153
+ |---|-----------|--------|---|---|---|
154
+ | 1 | North Star alignment | 25% | Unrelated to any target archetype | Adjacent — transferable skills apply | Exact target archetype match |
155
+ | 2 | CV match | 15% | <40% requirements covered | 60-75% covered, gaps are soft | 90%+ covered with proof points |
156
+ | 3 | Seniority fit | 15% | Junior / 2+ levels below | Mid-senior, manageable positioning | Staff+ or exact level match |
157
+ | 4 | Comp estimate | 10% | Well below market / no data | Median for role+location | Top quartile or above target |
158
+ | 5 | Growth trajectory | 10% | Dead end, no progression path | 1-2 yr path forward, timeline unclear | Clear path to next level |
159
+ | 6 | Remote quality | 5% | On-site only, no flexibility | Hybrid 2-3 days in office | Full remote, async-friendly |
160
+ | 7 | Company reputation | 5% | Red flags, poor reviews | Average employer, neutral signals | Top employer, strong brand |
161
+ | 8 | Tech stack modernity | 5% | Legacy, no AI/ML relevance | Mix of modern and legacy tooling | Frontier AI/ML stack (LLMs, agents, evals) |
162
+ | 9 | Speed to offer | 5% | 6+ month process, bureaucratic | Standard 4-6 week process | Fast-track, <4 weeks typical |
163
+ | 10 | Cultural signals | 5% | Bureaucratic, risk-averse | Mixed signals | Builder culture, high ownership |
164
+
165
+ **Final score** = weighted sum, rounded to 1 decimal (e.g., 4.2/5).
166
+
167
+ ### Score Emission — EMIT-ONCE JSON (REQUIRED)
168
+
169
+ **Before writing any report prose**, emit the score as a single JSON block. Then derive the narrative from the JSON — do NOT re-enumerate the 10 dimensions in thinking or prose. This rule exists because repeated scoring monologues are a major token-burn source: a typical eval session otherwise spends ~1,500 tokens narrating the same 10 scores two or three times.
170
+
171
+ **Shape** (emit exactly one block, nothing else in the block):
172
+
173
+ ```json
174
+ {
175
+ "report_num": "520",
176
+ "company": "Anthropic",
177
+ "role": "Manager, Forward Deployed Engineering",
178
+ "archetype": "AI Forward Deployed Engineer",
179
+ "url": "https://job-boards.greenhouse.io/anthropic/jobs/5099753008",
180
+ "date": "2026-04-15",
181
+ "scores": {
182
+ "north_star": { "score": 5, "rationale": "Exact FDE archetype match" },
183
+ "cv_match": { "score": 3, "rationale": "60% coverage; mgmt gap" },
184
+ "seniority_fit": { "score": 3, "rationale": "Senior IC, no formal mgmt" },
185
+ "comp": { "score": 5, "rationale": "$320-400K vs target $135-200K" },
186
+ "growth": { "score": 5, "rationale": "Founding team" },
187
+ "remote": { "score": 3, "rationale": "Hybrid, 25% in-office" },
188
+ "company": { "score": 5, "rationale": "Top AI brand" },
189
+ "stack": { "score": 5, "rationale": "Cutting-edge AI" },
190
+ "speed": { "score": 3, "rationale": "Standard process" },
191
+ "culture": { "score": 5, "rationale": "Builder culture" }
192
+ },
193
+ "weighted_total": 4.2,
194
+ "recommendation": "apply",
195
+ "pdf_threshold_met": true,
196
+ "draft_answers_threshold_met": true
197
+ }
198
+ ```
199
+
200
+ **Rules:**
201
+
202
+ - `score` per dimension is 1-5 (integer or 0.5 step).
203
+ - `rationale` is ≤ 80 characters, no markdown.
204
+ - `weighted_total` = round(Σ weight × score, 1). The 10 weights sum to 100% exactly.
205
+ - `recommendation` ∈ {`"apply"` (≥3.5), `"apply_with_caveats"` (3.0-3.4), `"skip"` (<3.0)}.
206
+ - `pdf_threshold_met` ⇔ `weighted_total ≥ 3.0`.
207
+ - `draft_answers_threshold_met` ⇔ `weighted_total ≥ 3.5`.
208
+
209
+ **After emitting the JSON:**
210
+
211
+ 1. Embed the same JSON block verbatim in the report `.md` under a `## Score` section (fenced as ` ```json `).
212
+ 2. Write Blocks A-F **referencing** the scores by key (e.g., "Seniority fit: 3/5 — Senior IC, no formal management"). Do NOT re-list all 10 dimensions in prose. Do NOT repeat the rationales verbatim.
213
+ 3. Do NOT narrate the scoring process in thinking before emitting the JSON. Decide, emit, move on.
214
+
215
+ **Score interpretation (use consistently everywhere):**
216
+ - **4.5-5.0** — Strong match. Generate PDF + draft answers. Apply promptly.
217
+ - **3.5-4.4** — Apply with tailored CV. Generate PDF + draft answers.
218
+ - **3.0-3.4** — Moderate match. Generate PDF. Flag gaps to candidate before applying.
219
+ - **< 3.0** — Weak match. Report only. Explicitly discourage applying unless candidate has a specific reason.
220
+
221
+ **PDF generation threshold:** >= 3.0 (consistent across all modes).
222
+ **Draft answer threshold:** >= 3.5 (consistent across all modes).
223
+
224
+ ## Scoring Calibration Anchors
225
+
226
+ **Moved:** the score anchors table now lives in `modes/_shared-calibration.md` — Read on-demand during evaluation, NOT loaded into the global `instructions` prefix. This split matters because the anchors churn as you accumulate reports; keeping them out of the cached prefix means each update doesn't bust cache for every unrelated session.
227
+
228
+ **Rule:** right before assigning a final score, Read `modes/_shared-calibration.md` once (per evaluation, not per dimension) and sanity-check your score against the anchors. Do not Read it more than once per evaluation.
229
+
230
+ ---
231
+
232
+ ## Global Rules
233
+
234
+ ### Skip these actions
235
+
236
+ 1. Invent experience or metrics
237
+ 2. Modify cv.md or portfolio files
238
+ 3. Submit applications on behalf of the candidate
239
+ 4. Share phone number in generated messages
240
+ 5. Recommend comp below market rate
241
+ 6. Generate a PDF without reading the JD first
242
+ 7. Use corporate-speak
243
+ 8. Ignore the tracker (every evaluated offer gets registered)
244
+ 9. Use AI-hallmark words: "leveraged", "utilized", "spearheaded", "orchestrated" (as metaphor), "cutting-edge", "passionate about", "drive innovation", "synergy", "holistic approach", "navigate complex", "foster collaboration". ATS platforms flag these. Use plain, specific verbs instead.
245
+
246
+ ### Do these always
247
+
248
+ 0. **Cover letter:** If the form has an option to attach or write a cover letter, ALWAYS include one. Generate PDF with the same visual design as the CV. Content: JD quotes mapped to proof points, links to relevant case studies. 1 page max.
249
+ 1. Read cv.md and article-digest.md (if exists) before evaluating any offer
250
+ 1b. **First evaluation of each session:** Run `node cv-sync-check.mjs` with Bash. If it reports warnings, notify the candidate before continuing
251
+ 2. Detect the role archetype and adapt framing
252
+ 3. Cite exact lines from CV when matching
253
+ 4. Use WebSearch for comp and company data
254
+ 5. Register in tracker after evaluating
255
+ 6. Generate content in the language of the JD (EN default)
256
+ 7. Be direct and actionable -- no fluff
257
+ 8. When generating English text (PDF summaries, bullets, LinkedIn messages, STAR stories): native tech English, not translated. Short sentences, action verbs, no unnecessary passive voice.
258
+ 8b. **Case study URLs in PDF Professional Summary:** If the PDF mentions case studies or demos, URLs MUST appear in the first paragraph (Professional Summary). The recruiter may only read the summary. All URLs with `white-space: nowrap` in HTML.
259
+ 9. **Tracker additions as TSV** -- NEVER add new entries directly to day files in `data/applications/`. Write TSV in `batch/tracker-additions/` and `merge-tracker.mjs` handles the merge.
260
+ 10. **Include `**URL:**` in every report header** -- between Score and PDF.
261
+
262
+ ### Use these tools
263
+
264
+ | Tool | Use |
265
+ |------|-----|
266
+ | WebSearch | Comp research, trends, company culture, LinkedIn contacts, fallback for JDs |
267
+ | WebFetch | Fallback for extracting JDs from static pages |
268
+ | Geometra MCP | Verify offers (`geometra_page_model`), extract JDs (`geometra_connect` + `geometra_snapshot`), fill forms (`geometra_fill_form`), generate PDFs (`geometra_generate_pdf`). **The MCP server accepts up to 5 concurrent sessions, but orchestrator subagent parallelism is capped at 2** (Hard Limit #1 in `AGENTS.md`) — do not interpret the 5-session ceiling as permission to dispatch 5 subagents. Each `geometra_connect` returns a `sessionId` — pass it to all subsequent tool calls to target the right session. Omitting `sessionId` targets the most recently connected session. Use `geometra_list_sessions` to inspect active sessions. |
269
+ | Read | cv.md, article-digest.md, cv-template.html |
270
+ | Write | Temporary HTML for PDF, day files in `data/applications/YYYY-MM-DD.md`, reports .md |
271
+ | Edit | Update tracker |
272
+ | Bash | `node generate-pdf.mjs` |
package/modes/apply.md ADDED
@@ -0,0 +1,257 @@
1
+ # Mode: apply — Live Application Assistant
2
+
3
+ Interactive mode for when the candidate is filling out an application form in Chrome. Reads what's on screen, loads prior context from the offer evaluation, and generates personalized answers for each form question.
4
+
5
+ ## Apply the session-length rule — REQUIRED
6
+
7
+ **If the candidate wants to apply to more than one job**, this mode MUST delegate each application to its own subagent with **max 2 in parallel** (Hard Limit #1 in `AGENTS.md`). For N jobs, run `ceil(N/2)` sequential rounds of 2. Never drive multi-job applications from a single interactive session: the accumulating Geometra tool results invalidate prompt caching and each message ends up re-processing 100K+ tokens of fresh history — see "Session Hygiene" in `.opencode/skills/job-forge.md`.
8
+
9
+ **DO NOT dispatch 3+ `task` calls in one message.** Two is the absolute ceiling. This is non-negotiable, even when the user asks for "apply to 10 jobs" — that becomes 5 rounds of 2, not one message with 10 dispatches.
10
+
11
+ For a single application interactively, carry on in the current session — the rule targets multi-job loops.
12
+
13
+ ### Run this multi-job apply runbook literally when N > 1
14
+
15
+ ```
16
+ Step 1 — Build the job list (N items)
17
+ Step 2 — Dedup: Grep data/pipeline.md + today's day file for each company+role. Drop any already APPLIED.
18
+ Step 3 — geometra_list_sessions() + geometra_disconnect({closeBrowser: true}) [once, before loop]
19
+ Step 4 — For round in ceil(N/2):
20
+ pair = jobs[round*2 : round*2 + 2]
21
+ # ONE message, 1 or 2 task() calls. Never 3.
22
+ task(apply to pair[0])
23
+ task(apply to pair[1]) # only if pair has 2
24
+ # WAIT for both returns. Do not proceed until both done.
25
+ Step 5 — Between rounds: geometra_list_sessions() + geometra_disconnect({closeBrowser: true})
26
+ Step 6 — Reconcile outcomes (Hard Limit #6):
27
+ bash: node merge-tracker.mjs # TSVs → day file
28
+ bash: node verify-pipeline.mjs # validate
29
+ Step 7 — Summarize outcomes; do NOT auto-retry failures.
30
+ ```
31
+
32
+ If a subagent fails, report it in the summary and let the user decide whether to retry. Never auto-retry — re-running a submit step risks duplicate applications.
33
+
34
+ **Outcome routing (Hard Limit #6 in `AGENTS.md`):**
35
+ - Subagents write `batch/tracker-additions/{num}-{slug}.tsv` — one TSV per job.
36
+ - Orchestrator runs `node merge-tracker.mjs` once at the end to consume TSVs into the right day file.
37
+ - **Do NOT** append APPLIED / FAILED / SKIP lines to `data/pipeline.md` — that file is the URL inbox only.
38
+
39
+ ## Verify these requirements
40
+
41
+ - **Best with Geometra MCP**: In visible proxy mode, the candidate sees the browser and opencode can interact with the page via `geometra_connect`, `geometra_form_schema`, and `geometra_fill_form`.
42
+ - **Without Geometra**: the candidate shares a screenshot or pastes the questions manually.
43
+
44
+ ## Run this workflow
45
+
46
+ ```
47
+ 1. DETECT → Read active Chrome tab (screenshot/URL/title)
48
+ 2. IDENTIFY → Extract company + role from the page
49
+ 3. SEARCH → Match against existing reports in reports/
50
+ 4. LOAD → Read full report + Section G (if it exists)
51
+ 5. COMPARE → Does the role on screen match the evaluated one? If changed → warn
52
+ 6. ANALYZE → Identify ALL visible form questions
53
+ 7. GENERATE → For each question, generate a personalized answer
54
+ 8. PRESENT → Show formatted answers for copy-paste
55
+ ```
56
+
57
+ ## Step 1 — Detect the offer
58
+
59
+ Run `geometra_connect` against the active page (with Geometra MCP), then `geometra_page_model` to read the title, URL, and visible content. **Do NOT also WebFetch the same URL** — Geometra's page model already contains the JD text. Each additional fetch re-pulls the same content into conversation history at full input cost.
60
+
61
+ **Without Geometra:** Ask the candidate to do one of these:
62
+
63
+ - Share a screenshot of the form (Read tool reads images).
64
+ - Paste the form questions as text.
65
+ - Provide company + role so we can look it up.
66
+
67
+ ## Step 2 — Extract context and search reports
68
+
69
+ - Extract the company name and role title from the page.
70
+ - Search in `reports/` by company name (Grep case-insensitive).
71
+ - If there's a match → load the full report.
72
+ - If there's a Section G → load the previous draft answers as a base.
73
+ - If there's NO match → notify and offer to run a quick auto-pipeline.
74
+
75
+ ## Step 3 — Detect role changes
76
+
77
+ Compare the role on screen against the evaluated one. When they differ, do the following.
78
+
79
+ - **Warn the candidate**: "The role has changed from [X] to [Y]. Do you want me to re-evaluate or adapt the answers to the new title?"
80
+ - **If adapt**: Adjust the answers to the new role without re-evaluating.
81
+ - **If re-evaluate**: Run a full A-F evaluation, update the report, regenerate Section G.
82
+ - **Update tracker**: Change the role title in the day file under `data/applications/` when the row already exists.
83
+
84
+ ## Step 4 — Scan form questions
85
+
86
+ Extract ALL visible questions across these field types.
87
+
88
+ - Free text fields (cover letter, why this role, additional info).
89
+ - Dropdowns (how did you hear, work authorization, source).
90
+ - Yes/No (relocation, visa sponsorship, current employment).
91
+ - Salary fields (range, expectation).
92
+ - Upload fields (resume, cover letter PDF).
93
+
94
+ Classify each question:
95
+ - **Already answered in Section G** → adapt the existing answer
96
+ - **New question** → generate an answer from the report + cv.md
97
+
98
+ ## Step 5 — Generate answers
99
+
100
+ Generate the answer for each question using these inputs in order.
101
+
102
+ <!-- isolint-disable-next-line undefined-step-reference -->
103
+ 1. **Report context**: Use proof points from Block B, STAR stories from Block F.
104
+ 2. **Previous Section G**: If a draft answer exists, use it as a base and refine
105
+ 3. **"I'm choosing you" tone**: Same framework as the auto-pipeline
106
+ 4. **Specificity**: Reference something concrete from the JD visible on screen
107
+ 5. **job-forge proof point**: Include in "Additional info" if there's a field for it
108
+
109
+ **Output format:**
110
+
111
+ ```
112
+ ## Answers for [Company] — [Role]
113
+
114
+ Based on: Report #NNN | Score: X.X/5 | Archetype: [type]
115
+
116
+ ---
117
+
118
+ ### 1. [Exact form question]
119
+ > [Answer ready for copy-paste]
120
+
121
+ ### 2. [Next question]
122
+ > [Answer]
123
+
124
+ ...
125
+
126
+ ---
127
+
128
+ Notes:
129
+ - [Any observations about the role, changes, etc.]
130
+ - [Personalization suggestions the candidate should review]
131
+ ```
132
+
133
+ ## Dispatch the form atomically (Step 5.5 — REQUIRED)
134
+
135
+ When the candidate asks you to actually submit (or when running in auto-pipeline mode at score ≥ 3.0), follow these rules **strictly**. Greenhouse-style forms regenerate internal field IDs after any DOM-mutating action, especially file uploads. That breaks multi-call fill sequences and forces the model into a retry loop that burns tens of thousands of tokens.
136
+
137
+ ### Use one `run_actions` call (Rule A — never split)
138
+
139
+ Do the entire submission in a **single** `geometra_run_actions` call that chains all steps. Never split upload / fill / submit across multiple tool calls.
140
+
141
+ ```
142
+ geometra_run_actions({
143
+ sessionId: "...",
144
+ actions: [
145
+ { type: "upload_files", fieldLabel: "Resume/CV", paths: ["/abs/path/cv.pdf"] },
146
+ { type: "fill_fields", valuesByLabel: { "First Name": "...", "Last Name": "...", ... } },
147
+ { type: "pick_listbox_option", fieldLabel: "Country", value: "United States" },
148
+ ... (one entry per choice/listbox) ...
149
+ { type: "click", labelOrText: "Submit application" }
150
+ ]
151
+ })
152
+ ```
153
+
154
+ ### Use `fieldLabel` over `fieldId` (Rule B)
155
+
156
+ Labels are stable across DOM refreshes; IDs are not. If `fieldLabel` works, use it everywhere. Only fall back to `fieldId` when two fields share the same label (rare — add a qualifier via sibling text instead).
157
+
158
+ ### Run recovery ONCE on ANY session error (Rule C)
159
+
160
+ **Trigger:** you see ANY of these error strings from a Geometra call:
161
+ - `Not connected`
162
+ - `session expired`
163
+ - `unknown session`
164
+ - `Failed to connect`
165
+ - `WebSocket` + `closed` / `error`
166
+
167
+ **Recovery sequence — run these FOUR calls in this EXACT order:**
168
+
169
+ ```
170
+ Call 1: geometra_list_sessions()
171
+ Call 2: geometra_disconnect({ closeBrowser: true })
172
+ Call 3: geometra_connect({
173
+ pageUrl: "<the same URL as before>",
174
+ isolated: true,
175
+ headless: true,
176
+ slowMo: 350
177
+ })
178
+ Call 4: geometra_run_actions({
179
+ sessionId: "<new sessionId from Call 3>",
180
+ actions: [... the EXACT same actions array you used before ...]
181
+ })
182
+ ```
183
+
184
+ ### Apply these recovery rules
185
+
186
+ 1. **Always run all 4 calls.** Do not skip Call 1 or Call 2 even if Call 1 shows an empty pool.
187
+ 2. **Do not re-fetch the form schema.** Do not call `geometra_form_schema` between Call 3 and Call 4. Your labels haven't changed, so the same `actions` array still works.
188
+ 3. **Do not edit the actions array.** Copy it verbatim from your first attempt. Do not re-pick fieldIds. Do not add or remove actions. Same array in, same array out.
189
+ 4. **Only ONE retry.** If Call 4 ALSO fails, STOP. Return this exact message to the orchestrator:
190
+
191
+ ```
192
+ APPLY FAILED AFTER RECOVERY: <URL>
193
+ Error 1: <first error message>
194
+ Error 2: <error after recovery>
195
+ Recommend: re-dispatch on @general-paid
196
+ ```
197
+
198
+ Do NOT try a third time. Do NOT try a different approach. The orchestrator will decide whether to re-dispatch on a bigger model.
199
+
200
+ ### Skip schema re-fetches mid-flow (Rule D)
201
+
202
+ `geometra_form_schema` returns hundreds of nested field IDs and pollutes context. Fetch it **at most once** per application, right after the initial `geometra_connect`, to discover the list of labels. After that, operate on labels only. Do not call `geometra_form_schema` again "to verify" — you're just paying for the same payload twice.
203
+
204
+ ### Skip mixed upload + separate fill (Rule E)
205
+
206
+ If you've uploaded a file with a dedicated `geometra_run_actions` call (e.g., the resume), and THEN try a separate `geometra_fill_form` or `geometra_fill_fields` call, the field IDs from the pre-upload schema are already stale. This was the primary failure mode on the Anthropic FDE apply trace — 4 retries, ~10K wasted tokens. The fix is Rule A: do everything in one shot.
207
+
208
+ ### Use two phases when the form has a post-upload conditional section (Exception)
209
+
210
+ Specific portals — Workday "parse my resume", iCIMS multi-step, SAP SuccessFactors — reveal additional fields ONLY after a file upload. In that case, use exactly two `run_actions` calls: (1) upload + wait_for, (2) fill+submit. After the first call, call `geometra_form_schema` **once** to discover the newly-revealed labels, then run the second call using labels. Never more than two phases.
211
+
212
+ ## Step 6 — Resolve OTP verification (if prompted)
213
+
214
+ Check for an OTP gate after the candidate (or Geometra) submits — the major portals (Greenhouse, Workday, Lever, Ashby) gate submission behind an email verification code. When an OTP step appears, do this.
215
+
216
+ 1. **Do NOT stop and ask the candidate to paste the code manually.** Use the Gmail MCP.
217
+ 2. Wait ~5-10 seconds for the email, then `gmail_list_messages` with a sender-scoped recency query (e.g. `from:greenhouse newer_than:10m`).
218
+ 3. `gmail_get_message` on the most recent match, extract the code from the body.
219
+ 4. `geometra_fill_otp` to enter it, then submit.
220
+
221
+ **Before reporting the submission as failed, always check Gmail.** A "submit did nothing" outcome usually means a silent OTP step — not a real failure.
222
+
223
+ Full sender-to-query table and fallback patterns: see "OTP Handling via Gmail MCP" in `AGENTS.md`.
224
+
225
+ ## Step 7 — Update outcomes after submission
226
+
227
+ Select the matching case below — two cases, two different flows. Do NOT mix them.
228
+
229
+ ### Update the existing row (Case A — prior `Evaluated` status)
230
+
231
+ The row exists. You are UPDATING an existing entry, which is allowed (Pipeline Integrity rule #2 in `AGENTS.md`):
232
+
233
+ 1. Find the existing row in `data/applications/YYYY-MM-DD.md` (or an older day file — use `rg` to locate it)
234
+ 2. Edit the `Status` column from `Evaluated` to `Applied` (or `FAILED` / `SKIP`)
235
+ 3. Append a confirmation note to the `Notes` column (e.g. OTP code, confirmation URL)
236
+ 4. Do NOT write a TSV. The row is already there.
237
+
238
+ ### Write a TSV addition (Case B — no prior evaluation row)
239
+
240
+ The row does NOT exist yet. You MUST go through the TSV pathway (Hard Limit #6 + Pipeline Integrity rule #1):
241
+
242
+ 1. Write `batch/tracker-additions/{num}-{slug}.tsv` with the canonical 9-column format (see "TSV Format for Tracker Additions" in `AGENTS.md`)
243
+ 2. At the end of the apply run, the orchestrator calls `node merge-tracker.mjs`, which inserts the row into today's day file
244
+ 3. Do NOT manually add a row to the day file. Do NOT append an `APPLIED` line to `data/pipeline.md`.
245
+
246
+ ### Apply to both cases
247
+
248
+ - Update Section G of the report with the final answers
249
+ <!-- isolint-disable-next-line undefined-step-reference -->
250
+ - Suggest next step: `/job-forge contact` for LinkedIn outreach — contact will automatically load this evaluation report and use the top proof points from Block B to craft targeted messages
251
+
252
+ ## Resolve long forms by scrolling
253
+
254
+ If the form has more questions than are visible:
255
+ - Ask the candidate to scroll and share another screenshot
256
+ - Or paste the remaining questions
257
+ - Process in iterations until the entire form is covered
@@ -0,0 +1,70 @@
1
+ # Mode: auto-pipeline — Full Automatic Pipeline
2
+
3
+ When the user pastes a JD (text or URL) without an explicit sub-command, execute the ENTIRE pipeline in sequence:
4
+
5
+ ## Step 0 — Extract JD
6
+
7
+ Fetch the JD content once. If the input is a **URL** (not pasted JD text), fetch the content **once** using exactly ONE of the methods below. **Do NOT chain methods as redundant fallbacks** — each method re-pulls the same 3-5K tokens into context.
8
+
9
+ **Pick exactly one method, in this priority order:**
10
+
11
+ 1. **Geometra MCP (preferred):** Most job portals (Lever, Ashby, Greenhouse, Workday) are SPAs. Use `geometra_connect` + `geometra_page_model` to render and read the JD. **If this returns non-empty JD text, STOP — do not WebFetch the same URL.**
12
+ 2. **WebFetch (only if Geometra is unavailable OR returned only a shell with no JD text):** For static pages (ZipRecruiter, WeLoveProduct, company career pages).
13
+ 3. **WebSearch (only if methods 1 AND 2 both failed):** Search for the role title + company on secondary portals that index the JD in static HTML.
14
+
15
+ **Rule:** Each URL gets fetched at most once per session. If you already have the JD text in context — from Geometra, a previous WebFetch, or pasted by the candidate — do not fetch again.
16
+
17
+ **If no method works:** Ask the candidate to paste the JD manually or share a screenshot.
18
+
19
+ **If the input is JD text** (not a URL): use it directly, no fetching needed.
20
+
21
+ ## Step 1 — Run Evaluation A-F
22
+ Execute exactly as in the `offer` mode (read `modes/offer.md` for all blocks A-F).
23
+
24
+ ## Step 2 — Save Report .md
25
+ Save the complete evaluation to `reports/{###}-{company-slug}-{YYYY-MM-DD}.md` (see format in `modes/offer.md`).
26
+
27
+ ## Step 3 — Generate PDF
28
+ Execute the full `pdf` pipeline (read `modes/pdf.md`).
29
+
30
+ ## Step 4 — Generate Application Answers When Score >= 3.5
31
+
32
+ Generate draft answers for the application form when the final score is >= 3.5. If the final score is >= 3.5 (per Canonical Scoring Model thresholds in `_shared.md`), generate draft answers for the application form:
33
+
34
+ 1. **Extract form questions**: Use Geometra MCP (`geometra_connect` + `geometra_form_schema`) to discover all form fields. If questions cannot be extracted, use the generic questions.
35
+ 2. **Generate answers** following the tone guidelines (see below).
36
+ 3. **Save in the report** as a `## G) Draft Application Answers` section.
37
+
38
+ ### Use Generic Questions When Form Extraction Fails
39
+
40
+ - Why are you interested in this role?
41
+ - Why do you want to work at [Company]?
42
+ - Tell us about a relevant project or achievement.
43
+ - What makes you a strong fit for this position?
44
+ - How did you hear about this role?
45
+
46
+ ### Apply This Tone For Form Answers
47
+
48
+ **Position: "I'm choosing you."** The candidate has options and is choosing this company for concrete reasons.
49
+
50
+ **Tone rules:**
51
+
52
+ - **Confident without arrogance**: "I've spent the past year building production AI agent systems — your role is where I want to apply that experience next".
53
+ - **Selective without overstatement**: "I've been intentional about finding a team where I can contribute meaningfully from day one".
54
+ - **Specific and concrete**: Always reference something REAL from the JD or the company, and something REAL from the candidate's experience.
55
+ - **Direct, no fluff**: 2-4 sentences per answer. No "I'm passionate about..." or "I would love the opportunity to...".
56
+ - **The hook is the proof, not the claim**: Instead of "I'm great at X", say "I built X that does Y".
57
+
58
+ **Framework per question:**
59
+ - **Why this role?** → "Your [specific thing] maps directly to [specific thing I built]."
60
+ - **Why this company?** → Mention something concrete about the company. "I've been using [product] for [time/purpose]."
61
+ - **Relevant experience?** → A quantified proof point. "Built [X] that [metric]. Sold the company in 2025."
62
+ - **Strong fit?** → "I sit at the intersection of [A] and [B], which is exactly where this role lives."
63
+ - **How did you hear?** → Honest: "Found through [portal/scan], evaluated against my criteria, and it scored highest."
64
+
65
+ **Language**: Always in the language of the JD (EN default). Apply `/tech-translate`.
66
+
67
+ ## Step 5 — Update Tracker
68
+ Update the current day file `data/applications/YYYY-MM-DD.md` with all columns including Report and PDF marked as ✅.
69
+
70
+ **If any step fails**, continue with the remaining steps and mark the failed step as pending in the tracker.