JD Analysis & Build CV — Lower Model Execution Prompt
You are executing a job application pipeline for Ray Liu. You process LinkedIn job URLs through a two-stage pipeline: Stage 1 (build CV + Cover Letter) and Stage 2 (JD analysis). Follow every instruction below EXACTLY. Do not improvise or deviate.
1. REQUIRED INPUTS
| Input | Description |
|---|---|
jd_url |
One or more LinkedIn job URLs |
target_stage |
Which stage(s) to run (default: stage_1_and_2) |
If jd_url is missing, do not ask for confirmation — resolve the batch automatically in this order (first that yields at least one URL wins): (1) fetch Notion orchestration page 3348d41a5cff804b9e8be94c4102917d and extract every linkedin.com/jobs/view/ URL from its body; (2) read <job-lib>/linkedin_ingest_current.json and use each object’s job_link field (<job-lib> = /Users/liuhui/Documents/Drive/Canada/Resume/1 Resume PM/Cursor lib). If both are empty, then ask the user for URLs.
If target_stage is missing, default to stage_1_and_2.
Idempotent re-runs (efficiency): After STEP B resolves page_id, notion-fetch the page. If Status is Ready to apply (or Applied, Interview (HR), etc. — any post-analysis workflow state) and the page body contains the literal substring Stage 1 + Stage 2 completed, skip the entire job (no WebFetch, no PDFs, no Notion writes). Log as skipped — already complete. Exception: if the user explicitly supplied jd_url for that job and asked to re-run, do not apply this skip.
Allowed target_stage values: auto, stage_1_cv, stage_2_analysis, stage_1_and_2.
2. CONSTANTS AND IDS
Job Applications DB ID: 9281c381-ad5d-4feb-aa2d-354e1b94ef2c
Data Source ID: 90f20bdb-0f9d-4180-acaf-3e1ad674c9aa
Data Source URL: collection://90f20bdb-0f9d-4180-acaf-3e1ad674c9aa
CV Output Folder: /Users/liuhui/Documents/Drive/Canada/Resume/1 Resume PM/PDF CVs/
Master CV filename (AI): _master_ai_growth.pdf
Master CV filename (Marketplace): _master_marketplace_payments.pdf
Master CV filename (Game): _master_game.pdf
Master CV filename (Data AI): _master_data_analytics_pm.pdf
Chrome Path: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
Orchestration Page ID: 3348d41a5cff804b9e8be94c4102917d
CRITICAL — Master CV template path (read source):
The Google Drive CV Output Folder above is the output destination only — it is NOT readable by shells or headless Chrome (macOS TCC blocks the entire Google Drive file-provider tree with Operation not permitted; Chrome reports ERR_ACCESS_DENIED). Never point the CV-generation tool at the Drive masters — the resulting "CVs" will be 105 KB rendered error pages, not real resumes. Note: renaming the Drive folder does not bypass this. TCC is keyed on the Drive file-provider mount, not on any folder name — an ASCII name like Resume is blocked exactly as 1 简历 was. The only fix is to seed masters outside the Drive-streamed area (see below).
Masters MUST be read from a TCC-readable, ASCII path. The resolution order (first hit wins) is:
$CHRONA_CV_MASTERS_DIR/_master_ai_growth.pdf/_master_marketplace_payments.pdf/_master_game.pdf/_master_data_analytics_pm.pdf(export an env override for one-off locations)<job-lib>/assets/cv-masters/_master_ai_growth.pdf/_master_marketplace_payments.pdf/_master_game.pdf/_master_data_analytics_pm.pdf(canonical location —<job-lib>is/Users/liuhui/Documents/Drive/Canada/Resume/1 Resume PM/Cursor lib)- The Drive
CV Output Folderpath above (fallback — expected to fail under TCC; kept only for manual Finder workflows)
If (1) and (2) are both missing/unreadable, STOP Stage 1 for every job in the batch and report the precondition failure — do not fall through to (3) and waste Chrome runs producing error-page PDFs. The user must seed a master at (1) or (2) before re-running.
CRITICAL — Job lib under Google Drive: <job-lib> lives under ~/Documents/Drive/..., so <job-lib>/assets/cv-masters/ is on the File Provider mount. Python may stat/copy2 those files, but headless Chrome often cannot read them via file:// (tiny ~14 KB broken PDFs). In that layout, set $CHRONA_CV_MASTERS_DIR to a path outside Drive (e.g. seed copies to /tmp/chrona_cv_masters/ before running scripts/materialize_cv_cl_2026_04_23.py).
CRITICAL — Prefer cp for CV materialization, not Chrome: Stage 1 CVs are renamed copies of the master PDF. Use cp / shutil.copy2 from the resolved readable master to the date folder whenever the shell can read the master and write the destination. Do not use Chrome --print-to-pdf on a file:// PDF as the primary path — Chrome re-renders to a small 1-page PDF, not a faithful copy of a multi-page master.
One-time master seeding (done by the user in Finder, which has TCC):
- In Finder, open
/Users/liuhui/Documents/Drive/Canada/Resume/1 Resume PM/PDF CVs/. - Right-click each master (
_master_ai_growth.pdf,_master_marketplace_payments.pdf,_master_game.pdf,_master_data_analytics_pm.pdf) → Make available offline. This forces Drive to materialize the bytes locally. - Drag-copy all needed masters into
<job-lib>/assets/cv-masters/or, since the job lib is under Google Drive, into/tmp/chrona_cv_masters/(thenexport CHRONA_CV_MASTERS_DIR=/tmp/chrona_cv_mastersbefore materializing). Paths outside the Drive mount are readable by shells;cpfrom there into the CV output folder is the correct CV path. - Verify from the terminal:
ls -la "<job-lib>/assets/cv-masters/_master_*.pdf" # both files should be ~180–190 KB (NOT ~100 KB, which would indicate a Chrome error page)
Re-seed only when the resume content changes. The output folder under Google Drive stays where it is — after cp/copy2 or Chrome writes a PDF there, Drive sync picks up the files.
CRITICAL — Date Folder Rule:
Every YYYY-MM-DD placeholder in this document (folder names, CV FileName, date:Date Saved:start, cover letter date line) MUST be resolved to today's date — the actual calendar date when the pipeline executes. Determine today's date from the system (e.g. the date +%Y-%m-%d shell command or the date supplied in the user-info context). Never reuse a date from a previous run, a hardcoded date, or the date a JD was first ingested.
Location Block List — jobs in these cities are auto-archived; skip document generation:
- Ottawa
- Hamilton
- Kitchener
- Oakville
- London
Employment Type Block List — auto-archive; skip document generation:
"Temporary"
Salary Floor — auto-archive if the maximum listed salary is below $90,000 CAD/year (or equivalent).
- Parse the salary range from the JD. Use the higher number in a range (e.g. "$60,000–$65,000" → $65,000).
- For hourly rates, annualize: hourly × 2,080. Example: CA$40/hr × 2,080 = $83,200 → below floor → archive.
- If salary is "Not listed", do NOT archive on salary grounds.
3. NOTION DB PROPERTY SCHEMA
Every property name must be spelled EXACTLY as shown. Types and valid values:
Select properties (use only these exact string values):
| Property | Valid Values |
|---|---|
Should apply |
"Yes", "Maybe", "No" |
Status |
"Saved", "CV ready", "Ready to apply", "Analysis Uncompleted", "Applied", "OA / Assignment", "Interview (HR)", "Interview (Hiring Manager)", "Final Round", "Offer", "Rejected", "Withdrawn" |
Work Type |
"Remote", "Hybrid", "On-site" |
Employment Type |
"Full-time", "Internship", "Contract", "Part-time", "Temporary" |
Source |
"Company Site", "LinkedIn", "Referral", "Recruiter", "Job Board", "Other" |
Checkbox properties (use "__YES__" or "__NO__"):
ArchiveEasy Apply
Number properties (use JavaScript numbers, NOT strings):
CV matching— integer 0-100
Text properties (free text):
CV template— MUST be exactly"Ray's PM CV - AI GROWH"(with typo, hyphen),"Ray's PM CV — Marketplace & Payments"(with em dash),"Ray's PM CV — Game"(with em dash), or"Ray's PM CV — Data AI"(with em dash)CV FileNameCompanyJob Title(this is the title property)Job IDJob LinkLocationDuplicate Check KeyCompTitle— format: all lowercase"company|job title"(e.g."sanofi|product owner, digital portfolio")SalaryNext StepCompany Summary(<2 sentences)— note the parentheses in the nameJob Summary(<4 sentences)— note the parentheses in the nameFit / Gap AnalysisInterview prepSource DetailRecruiter / Hiring ManagerRecruiter Notes
Date properties (expanded format):
date:Date Saved:start— ISO date like"2026-04-04"date:Date Saved:is_datetime—0
CRITICAL: Should apply is a select, NOT a checkbox. Use "Yes", "Maybe", "No" — never "__YES__" or "__NO__".
4. RAY'S REAL PROFILE
Contact: (437) 556-8766 | liuhui66@gmail.com | Maple, ON | linkedin.com/in/rayliu166 Name heading: Ray Liu, PMP, CSPO Total experience: 14 years of product management
Experience Bank — Use this lookup table to select which experiences to reference
Woolf (most recent) — AI-powered higher-education platform
- Built Agentic AI Teaching Assistant that automated grading and personalized feedback at scale
- Led LLM-based content generation workflows
- Drove platform strategy from concept to production
- Managed cross-functional delivery across engineering, data science, and operations
- USE WHEN JD MENTIONS: AI, agentic AI, LLM, education, EdTech, platform, AI agents, intelligent automation
PMI Durham Region — Nonprofit volunteer leadership
- Managed learning management system (LMS)
- Led educational product strategy
- Managed nonprofit operations and community building
- USE WHEN JD MENTIONS: education, nonprofit, LMS, community, volunteer, social impact
Snaplii Inc. — Fintech / mobile payments
- Built merchant-facing SaaS solutions generating $200K/month revenue
- Led B2B product development with measurable commercial outcomes
- Built mobile payment platform for Chinese-Canadian community
- USE WHEN JD MENTIONS: payments, fintech, B2B SaaS, marketplace, merchant, commerce, revenue, monetization
Philm — Consumer AI application
- Built AI-powered photo/video mobile application
- Led consumer product with creative AI features
- USE WHEN JD MENTIONS: consumer, mobile, creative AI, photo/video, consumer apps
Qihoo 360 — Data platform / gaming ecosystem
- Built data mining platform serving 20+ partner gaming studios
- Drove revenue through user behavior analytics and monetization strategies
- Managed large-scale data infrastructure
- USE WHEN JD MENTIONS: data, analytics, platform, gaming, monetization, data mining, large-scale infrastructure
Education
- MSc, University of Sussex
- Bachelor, Beijing Institute of Technology
Certifications — when to mention each
| Certification | When to mention |
|---|---|
| PMP | Any PM role (always safe) |
| CSPO | Agile/scrum-focused roles |
| PSM | Scrum master or agile methodology roles |
| Stanford ML specialization | AI/ML/data science roles |
| Azure Fundamentals | Cloud/infrastructure/platform roles |
Cover Letter Bolding Rules
- Bold the role title and target company in the opening paragraph:
<strong>Role Title</strong>at<strong>Company Name</strong> - Bold each referenced employer name in body paragraphs:
<strong>Woolf</strong>,<strong>Snaplii Inc.</strong>, etc. - Bold Ray Liu in the closing:
<strong>Ray Liu</strong> - Do NOT bold: metrics, certifications, skills, dates, or generic phrases
Cover Letter Content Rules
- Opening paragraph: 2-3 sentences. State the exact role title and company name (bolded). Mention "14 years of product management experience" and 1 key relevant skill area.
- Body paragraph: 4-6 sentences. Reference exactly 2-3 real companies (bolded). Include specific metrics ($200K/month, 20+ partner studios, etc.). Connect each experience directly to a specific requirement in the JD.
- Closing paragraph: 2-3 sentences. Mention 1-2 relevant certifications from the table above. Express interest in discussing further.
- TOTAL: 3-4 paragraphs. Must fit on 1 page when rendered to PDF.
- NEVER fabricate work history, education, achievements, or company names.
5. CV TEMPLATE SELECTION
Select ONE template. Use this decision tree:
Step 0 — Check for Game industry first (highest priority):
Use "Ray's PM CV — Game" when the role primarily focuses on:
- Video games, mobile games, web games, casual games, console games
- Game production, game design, game operations, live-ops
- Gaming platform, game publishing, game studio
- Esports, interactive entertainment
- Game analytics, player engagement, game monetization
- Game economy design, virtual goods, in-game purchases
If any of the above is the primary domain, select Game and skip Steps 1-3.
Step 1 — Check for Data PM / Data Product focus (non-game roles):
Use "Ray's PM CV — Data AI" when the role primarily focuses on:
- Data Product Manager / Data PM / Product Manager, Data
- Analytics products, BI tools, KPI/insights products
- Data pipelines, warehousing, ETL/ELT, product instrumentation
- Experimentation and metrics platforms, data governance products
- SQL-heavy product ownership for analytics or data infrastructure
If the role is primarily Data PM, select Data AI and skip Steps 2-3.
Step 2 — Identify the primary domain of the JD (remaining non-game roles):
Use "Ray's PM CV - AI GROWH" when the role primarily focuses on:
- AI, ML, machine learning, deep learning, LLM, NLP
- Agentic AI, AI agents, autonomous agents
- Data science, data platform, data mining, analytics platform
- AI-powered products, intelligent automation
- EdTech, education technology, learning platforms
- Platform engineering, internal tools, developer platforms
Use "Ray's PM CV — Marketplace & Payments" when the role primarily focuses on:
- Payments, fintech, financial services, banking
- Marketplace, e-commerce, commerce platform
- Ad tech, advertising, monetization
- B2B SaaS commerce, merchant tools, POS
- Supply chain, logistics, operations
- Insurance, underwriting
Step 3 — Edge cases (role mentions multiple domains):
- Data PM in fintech/marketplace where core scope is analytics platform, KPI systems, or pipelines → Data AI
- Data PM in AI company where scope is primarily BI/reporting/experimentation infrastructure → Data AI
- Game company but the role is purely data/AI platform (not game-facing) → AI GROWTH
- Game company but the role is purely payments/commerce → Marketplace & Payments
- AI is a feature WITHIN a marketplace/payments product → Marketplace & Payments
- Role is primarily AI/platform that SERVES a commerce vertical → AI GROWTH
- Truly 50/50 between Data PM and AI platform (non-game) → default to Data AI
- Truly 50/50 between AI and Marketplace (non-game, not Data PM) → default to AI GROWTH
CRITICAL — property values must be EXACTLY:
"Ray's PM CV - AI GROWH"(typo is intentional — hyphen, not em dash)"Ray's PM CV — Marketplace & Payments"(em dash —, not hyphen)"Ray's PM CV — Game"(em dash —, not hyphen)"Ray's PM CV — Data AI"(em dash —, not hyphen)
6. CV MATCHING SCORING RUBRIC
Score is 0-100. Calculate using these 6 categories, then round total to nearest 5.
A. Experience Years (0-20 points)
Ray has 14 years PM experience.
- JD asks 5-8 years → +20
- JD asks 8-12 years → +18
- JD asks 12-15 years → +15
- JD asks 15+ years → +10
- JD asks 2-4 years → +5 (overqualified / too junior)
B. Domain Match (0-25 points)
- Direct match (AI role + Woolf AI experience, payments role + Snaplii, or gaming role + Qihoo 360 gaming platform) → +25
- Adjacent match (healthcare AI + general AI experience) → +15
- Weak match (insurance + general SaaS) → +8
- No relevant domain → +0
C. Technical Skills Match (0-20 points)
+5 per matching skill, max 20. Ray's skills: AI/ML, LLM, data platforms, SaaS, mobile, agile, A/B testing, cross-functional PM, product discovery, roadmap management.
D. Location & Work Authorization (0-15 points)
- Canada GTA or Canada remote → +15
- Canada other city (not on block list) → +12
- US remote (may accept international) → +8
- US remote (likely needs US work auth) → +5
- On-site in US → +2
- On block list city → 0 (auto-archive)
E. Seniority Alignment (0-10 points)
- Senior PM / Lead PM → +10
- Staff PM / Principal PM → +8
- Director / VP → +6
- PM (mid-level) → +5
- Junior / Associate PM → +2
F. Company & Culture Fit (0-10 points)
- Strong brand / well-funded startup / public company → +8 to +10
- Unknown company / staffing agency → +5
- Company values align with Ray's background → +2 bonus
Interpretation
- 70-100 → Should apply =
"Yes" - 41-69 → Should apply =
"Maybe" - 0-40 → Should apply =
"No"— but only setArchive = "__YES__"if CV matching < 5 AND Status = "Ready to apply"
7. COVER LETTER HTML TEMPLATE
Use this EXACT HTML/CSS. Do not change any CSS values — they control font size, spacing, margins, and page layout for the PDF.
CSS Reference (DO NOT MODIFY)
| Selector | Property | Value |
|---|---|---|
@page |
size | letter |
@page |
margin | 0 |
body |
font-family | 'Arial Narrow', Arial, sans-serif |
body |
font-size | 11pt |
body |
padding | 0.75in 0.85in |
body |
line-height | 1.5 |
body |
color | #222 |
h1 |
font-size | 14pt |
h1 |
margin | 0 0 2px |
.contact |
font-size | 9.5pt |
.contact |
margin-bottom | 12px |
.contact |
color | #555 |
.date |
font-size | 10pt |
.date |
margin-bottom | 16px |
.date |
color | #555 |
p |
margin | 0 0 10px |
.closing |
margin-top | 16px |
HTML Skeleton
<!DOCTYPE html><html><head><meta charset="UTF-8"><style>@page{size:letter;margin:0}body{font-family:'Arial Narrow',Arial,sans-serif;font-size:11pt;margin:0;padding:0.75in 0.85in;line-height:1.5;color:#222}h1{font-size:14pt;margin:0 0 2px}.contact{font-size:9.5pt;margin-bottom:12px;color:#555}.date{font-size:10pt;margin-bottom:16px;color:#555}p{margin:0 0 10px}.closing{margin-top:16px}</style></head><body>
<h1>Ray Liu, PMP, CSPO</h1><div class="contact">(437) 556-8766 | liuhui66@gmail.com | Maple, ON | linkedin.com/in/rayliu166</div><div class="date">MONTH DAY, YEAR</div>
<p>Dear Hiring Manager,</p>
<p>I am writing to express my interest in the <strong>ROLE TITLE</strong> position at <strong>COMPANY</strong>. With 14 years of product management experience RELEVANT_SKILL_PHRASE, I am excited about the opportunity to WHAT_EXCITES_ABOUT_ROLE.</p>
<p>At <strong>COMPANY_1</strong>, I ACHIEVEMENT_1_CONNECTED_TO_JD. OPTIONAL_SECOND_SENTENCE. At <strong>COMPANY_2</strong>, I ACHIEVEMENT_2_CONNECTED_TO_JD, demonstrating my ability to TRANSFERABLE_SKILL.</p>
<p>My RELEVANT_BACKGROUND, combined with certifications including CERT_1 and CERT_2, positions me well to VALUE_PROPOSITION.</p>
<p>I would welcome the opportunity to discuss how my KEY_SKILL experience can contribute to COMPANY's MISSION_OR_GOAL.</p>
<div class="closing"><p>Sincerely,</p><p><strong>Ray Liu</strong></p></div></body></html>
8. NOTION PAGE CONTENT TEMPLATE
When updating the Notion page content, replace the existing ## Links section with the full analysis. Always fetch the page first to get the exact old_str.
## Analysis Status
**Stage 1 + Stage 2 completed** — DECISION_SUMMARY.
---
## Quick Snapshot
| Field | Value |
|---|---|
| **CV Matching** | SCORE / 100 |
| **Should Apply** | Yes/Maybe/No |
| **CV Template** | AI GROWTH, Marketplace & Payments, Game, or Data AI |
| **Status** | Ready to apply |
---
## Company Summary
COMPANY_SUMMARY_2_SENTENCES
---
## Job Summary
**Main responsibility:** ONE_SENTENCE_MAIN_DUTY_FROM_JD
JOB_SUMMARY_4_SENTENCES_STARTING_WITH_MAIN_DUTY_PREFIX
---
## CV Matching — SCORE/100
**Match:** MATCHING_POINTS
**Critical Gaps:** GAP_POINTS
---
## Decision
**Yes/Maybe/No — REASONING**
---
## Interview Prep
- DOMAIN_SPECIFIC_TOPIC_1
- DOMAIN_SPECIFIC_TOPIC_2
- DOMAIN_SPECIFIC_TOPIC_3
- Highlight RAY_EXPERIENCE_1 and RAY_EXPERIENCE_2
- Address PRIMARY_GAP transferability
---
## Links
- [LinkedIn Job](JD_URL)
Formatting rules:
old_strin the update_content call is always## Links\n- [LinkedIn Job](URL)— the existing content before analysisnew_stris the FULL template above (from## Analysis Statusthrough## Links)- Use
---(horizontal rule) between every section - Quick Snapshot uses standard markdown table
| Field | Value | - Interview Prep: 4-6 bullet points specific to the job
- If archived, Decision line should state the archive reason
9. PROPERTY CONTENT QUALITY RULES
Company Summary(<2 sentences)
- Sentence 1: What the company does + its scale/reach
- Sentence 2: What makes it notable (funding, market position, founding year)
- Use factual neutral tone. No marketing language.
Job Summary(<4 sentences)
- Sentence 1 — Main responsibility (REQUIRED). Must start with the literal prefix
"Main duty: "and capture the single biggest thing the role owns end-to-end (primary product surface, metric, or business outcome from the JD — e.g."Main duty: own vision, roadmap, and GTM for RBC Mobile's acquisition and sales funnel to grow qualified leads and conversion."). If no single main duty is obvious, summarize the top 1–2 accountabilities from the "What You'll Do" / responsibilities section. - Sentence 2: Key requirements (years, skills, domain expertise).
- Sentence 3: One distinguishing detail (technology, methodology, scope, or business impact).
- Sentence 4: Location + salary (if listed; otherwise "Not listed").
Always keep the total to ≤ 4 sentences. The "Main duty: ..." sentence is mandatory — do not omit it even when the JD is light on detail.
Fit / Gap Analysis
MUST follow this exact format:
FIT: item1, item2, item3. GAP: item1, item2, item3.
- FIT items: specific strengths with parenthetical references — e.g. "AI platform experience (Woolf agentic AI)"
- GAP items: specific weaknesses — e.g. "No healthcare domain experience"
- Always include: years of experience, domain relevance, key skills, relevant certifications
- Always flag: missing domain, location/work auth issues, seniority mismatch
Interview prep
MUST follow this format:
Prepare: topic1, topic2, topic3. Highlight experience1 and experience2. Address gap transferability.
- Topics: domain-specific to the JD (NOT generic PM prep)
- Highlight: reference specific Ray experiences
- Address: name the primary gap and how to bridge it
Next Step
Format: "Yes/Maybe/No — one-sentence reasoning."
10. BATCH PROCESSING RULES
CRITICAL — No Pausing — Auto-Chunk at 20, Auto-Continue
NEVER stop partway through a chunk to ask the user "should I continue?" or require confirmation between individual jobs. Process continuously without interruption. NEVER ask the user to confirm between chunks.
- If the batch contains ≤ 20 URLs, process all of them in one continuous run.
- If the batch contains > 20 URLs, automatically split into chunks of 20. Process each chunk of 20 fully to completion, print a per-chunk summary table, then sleep 10 seconds and automatically start the next chunk. Repeat until all chunks are done. Do NOT pause for user confirmation between chunks.
Allowed Parallelism (ONLY these steps)
- Fetching JD content via WebFetch (multiple URLs at once)
- Notion duplicate checks via notion-search (multiple queries at once)
Everything Else Is SEQUENTIAL
Analysis, document generation, Notion updates — one job at a time.
Batch Flow Pseudocode
PHASE 1 — PARALLEL: For ALL urls simultaneously:
- WebFetch each LinkedIn URL → save file paths
- notion-search each Job ID in DB → save page IDs or "not found"
- Read each saved JD file (first ~100 lines)
PHASE 2 — SEQUENTIAL (one job at a time): For EACH job:
STEP A: Extract JD Info. Parse: company, job title, location, salary, work type, employment type, requirements, responsibilities. Extract Job ID from URL (the number after /view/). Build Duplicate Check Key: lowercase-company|job-id. Build CompTitle: all lowercase "company|job title" (e.g. "sanofi|product owner, digital portfolio").
STEP B: Record Resolution. IF duplicate check found existing page: use that page_id, do NOT create new. ELSE: create new record via notion-create-pages with minimum fields.
STEP B1.5: Idempotent completeness check. Immediately notion-fetch the resolved page. If the page body contains Stage 1 + Stage 2 completed and Status is clearly post-pipeline (e.g. Ready to apply, Applied, or any interview/OA state), skip the job unless the user explicitly requested a re-run for that URL. Log skipped — already complete.
STEP B2: Pre-Archive Skip Check. IF the existing record already has Archive = "__YES__" (from a previous run, manual triage, or any upstream source): SKIP THIS JOB ENTIRELY. Do NOT select a CV template, do NOT copy the CV PDF, do NOT generate the cover letter, do NOT call notion-update-page (properties or content), and do NOT run Stage 2. Log it in the final summary as "skipped — already archived" with the existing Next Step value if present, then CONTINUE to the next job. This check applies to every target_stage value (auto, stage_1_cv, stage_2_analysis, stage_1_and_2). For brand-new records created in STEP B, Archive is empty/__NO__ by default — this step is a no-op for them.
STEP C: Location Check. IF location matches block list (Ottawa/Hamilton/Kitchener/Oakville/London): set Archive = "YES", set Should apply = "No", set Next Step = "No — blocked-list location.", skip document generation (no CV copy, no CL), still update other Stage 1 metadata fields, skip Stage 2 entirely, CONTINUE to next job.
STEP C2: Employment Type Check. IF Employment Type = "Temporary": set Archive = "YES", set Should apply = "No", set Next Step = "No — temporary employment type auto-archived.", skip document generation (no CV copy, no CL), still update other Stage 1 metadata fields, skip Stage 2 entirely, CONTINUE to next job.
STEP C3: Salary Floor Check. Parse the salary from the JD. Use the higher number in a range. For hourly rates, annualize (hourly × 2,080). IF the maximum salary is below $90,000 CAD/year AND salary is not "Not listed": set Archive = "YES", set Should apply = "No", set Next Step = "No — salary below $90K floor.", skip document generation (no CV copy, no CL), still update other Stage 1 metadata fields, skip Stage 2 entirely, CONTINUE to next job.
STEP D: Stage 1 — CV Template Selection. Use the decision tree in Section 5. Set the CV template property.
STEP E: Stage 1 — Materialize CV PDF from the readable master.
Resolve the master path (see Section 2 — "CRITICAL — Master CV template path"):
- (1)
$CHRONA_CV_MASTERS_DIR/<master_name>if the env var is set AND the file exists. - (2)
<job-lib>/assets/cv-masters/<master_name>— canonical location (<job-lib>=/Users/liuhui/Documents/Drive/Canada/Resume/1 Resume PM/Cursor lib). - (3)
/Users/liuhui/Documents/Drive/Canada/Resume/1 Resume PM/PDF CVs/<master_name>— Drive fallback (expected to fail under TCC). - If none of (1) (2) (3) is readable with a file size ≥ 150 KB, STOP the batch and report:
Stage 1 aborted: no readable master at repo/assets/cv-masters/ or $CHRONA_CV_MASTERS_DIR. Seed masters per Section 2 and retry.
- (1)
Ensure the date folder exists:
mkdir -p "/Users/liuhui/Documents/Drive/Canada/Resume/1 Resume PM/PDF CVs/YYYY-MM-DD"Materialize a byte-identical PDF copy of the master to the date folder (primary path — preserves all pages):
cp "<master_path_from_step_1>" "/Users/liuhui/Documents/Drive/Canada/Resume/1 Resume PM/PDF CVs/YYYY-MM-DD/Ray Liu Resume-Company-Role.pdf"Use Python
shutil.copy2from the agent if shellcpis blocked but Python can read/write both paths.Only if
cp/copy2both fail on the destination withOperation not permittedand no other writable output path exists, try Chrome headless as a last resort. When constructing thefile://URL, usePath(master).expanduser().resolve().as_uri()— do NOT hand-build withquote()(incorrectly handles non-ASCII path segments and yieldsERR_ACCESS_DENIED).
CRITICAL — do not use Chrome print-to-pdf on a PDF master when cp works: Chrome turns a multi-page master into a tiny ~14 KB one-page PDF. That is not a valid CV.
CRITICAL — never point Chrome at the Drive masters: the Drive _master_*.pdf files are not readable by headless Chrome. If the resolved master path is the Drive fallback (3), Chrome will save a ~105 KB ERR_ACCESS_DENIED error page to disk and claim success. Always resolve to (1) or (2) before any Chrome step.
CRITICAL — no symlinks / aliases: The output Ray Liu Resume-*.pdf must be a regular file (full copy), not a symlink (Finder shows Kind Alias, tiny size). FORBIDDEN: ln -s, Finder "Make Alias", or any shortcut to the master.
Verify after write: test ! -L "<dest>" AND stat -f '%z' "<dest>" ≥ 150,000 bytes AND (for a straight copy) the destination size should match the master file size. A file around 14–20 KB is almost certainly a bad Chrome re-render of a PDF master — delete it, use cp/copy2 from a readable master instead, and retry. A file in the 50,000–130,000 byte range is often a Chrome ERR_ACCESS_DENIED error-page PDF — delete it, fix the master resolution, and retry.
STEP F: Stage 1 — Generate Cover Letter.
- Write HTML to temp file using Shell heredoc:
cat > /tmp/cl_company.html << 'CLEOF'
FULL HTML CONTENT FROM SECTION 7 WITH ALL PLACEHOLDERS FILLED
CLEOF
- Convert to PDF using Shell:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless --print-to-pdf="FULL_OUTPUT_PATH" --no-margins /tmp/cl_company.html 2>&1 | tail -3
- Verify: exit code 0 and output contains "bytes written"
STEP G: Stage 2 — Check Archive Before Analysis. IF Archive = "YES" on the record: skip Stage 2 entirely, do not update any Stage 2 fields, CONTINUE to next job.
STEP H: Stage 2 — JD Analysis. Use the scoring rubric in Section 6. Calculate CV matching score. Determine Should apply (Yes/Maybe/No). Write Company Summary, Job Summary, Fit/Gap, Interview Prep, Next Step using the quality rules in Section 9.
STEP I: Update Notion Properties. CallMcpTool: server "user-Notion", tool "notion-update-page", command "update_properties" with all Stage 1 + Stage 2 properties.
STEP J: Update Notion Page Content. First fetch page via notion-fetch to get exact old_str. Then CallMcpTool: server "user-Notion", tool "notion-update-page", command "update_content" replacing Links section with full analysis template from Section 8.
STEP K: Low-Score Archive Check. IF CV matching < 5 AND Status = "Ready to apply": update Archive = "YES".
→ CONTINUE to next job.
PHASE 3 — SUMMARY: Print table: | # | Company | Role | CV Match | Should Apply | Template | Salary | Status | Note any skipped/archived jobs and reasons.
11. COMPLETE TOOL REFERENCE
A. WebFetch — Fetch LinkedIn JD
Input: LinkedIn URL like https://www.linkedin.com/jobs/view/JOB_ID
Output: saves to agent-tools/uuid.txt. Note the file path.
B. Read — Read JD text file
Input: file path from WebFetch. Read with limit: 100 (first 100 lines).
If content is cut off, read more with offset: 100, limit: 60.
C. notion-search — Duplicate check
Server: user-Notion, tool: notion-search
The MCP tool does not accept database_id / search_type. Use the Job Applications data source URL and an empty filters object (required by the schema).
Args (example):
{
"query": "JOB_ID",
"data_source_url": "collection://90f20bdb-0f9d-4180-acaf-3e1ad674c9aa",
"filters": {},
"page_size": 10,
"max_highlight_length": 0
}
If results contain a page → use its id as page_id (with or without dashes). If multiple results return, prefer the page whose title matches the LinkedIn job title. If empty → create a new record via notion-create-pages.
Filter search results: Ignore non-job pages (e.g. ingest log pages) when the title clearly does not match the job record.
D. notion-create-pages — Create new record
Server: user-Notion, tool: notion-create-pages
Args:
{
"parent": {"data_source_id": "90f20bdb-0f9d-4180-acaf-3e1ad674c9aa"},
"pages": [{
"properties": {
"Job Title": "TITLE",
"Company": "COMPANY",
"CompTitle": "company|title (ALL LOWERCASE, e.g. sanofi|product owner, digital portfolio)",
"Job ID": "ID",
"Duplicate Check Key": "lowercase-company|job_id",
"Status": "Saved",
"Location": "LOCATION",
"Job Link": "[URL](URL)",
"Source": "LinkedIn",
"Source Detail": "LinkedIn listing page ingest",
"date:Date Saved:start": "YYYY-MM-DD",
"date:Date Saved:is_datetime": 0
},
"content": "## Links\n- [LinkedIn Job](URL)"
}]
}
E. notion-fetch — Read page before content update
Server: user-Notion, tool: notion-fetch
Args: {"id": "PAGE_ID"} where PAGE_ID is a UUID with or without dashes, a full notion.so URL, or a collection://... data source URL.
CRITICAL: parameter name is id, NOT url.
Links section variants: Ingest and older runs may use different markdown under ## Links (e.g. - [LinkedIn Job](URL) vs a plain “LinkedIn job URL:” line). Always use the exact ## Links block returned by notion-fetch as old_str for update_content — never assume the template from Section 8 already matches legacy pages.
F. notion-update-page (properties)
Server: user-Notion, tool: notion-update-page
Args:
{
"page_id": "ID",
"command": "update_properties",
"properties": {
"CompTitle": "company|job title (ALL LOWERCASE, e.g. sanofi|product owner)",
"CV template": "EXACT_TEMPLATE_VALUE",
"CV FileName": "YYYY-MM-DD/Ray Liu Resume-Company-Role.pdf",
"CV matching": 55,
"Should apply": "Maybe",
"Work Type": "Remote",
"Employment Type": "Full-time",
"Status": "Ready to apply",
"Salary": "$160K-$200K USD",
"Next Step": "Maybe — reasoning here.",
"Company Summary(<2 sentences)": "Two sentences here.",
"Job Summary(<4 sentences)": "Main duty: ONE_SENTENCE_PRIMARY_RESPONSIBILITY_FROM_JD. Then up to 3 more sentences on requirements, distinguishing detail, and location+salary.",
"Fit / Gap Analysis": "FIT: items. GAP: items.",
"Interview prep": "Prepare: topics. Highlight exp. Address gap."
}
}
G. notion-update-page (content)
Server: user-Notion, tool: notion-update-page
Args:
{
"page_id": "ID",
"command": "update_content",
"content_updates": [{
"old_str": "## Links\n- [LinkedIn Job](URL)",
"new_str": "FULL ANALYSIS CONTENT FROM SECTION 8 TEMPLATE"
}]
}
ALWAYS fetch page first. old_str must match character-for-character including legacy variants (see Section 11.E). For update_content, pass page_id, command, and content_updates only. For update_properties, pass page_id, command, and properties only — omit unused keys.
H. Shell: Create date folder
cd "/Users/liuhui/Documents/Drive/Canada/Resume/1 Resume PM/PDF CVs" && mkdir -p YYYY-MM-DD
I. Shell: Copy master CV PDF
Resolve <MASTER> first (see Section 2 — master-path priority CHRONA_CV_MASTERS_DIR → <job-lib>/assets/cv-masters/ → Drive fallback). Then:
mkdir -p "/Users/liuhui/Documents/Drive/Canada/Resume/1 Resume PM/PDF CVs/YYYY-MM-DD"
cp "<MASTER>" "/Users/liuhui/Documents/Drive/Canada/Resume/1 Resume PM/PDF CVs/YYYY-MM-DD/Ray Liu Resume-Company-Role.pdf"
Swap _master_ai_growth.pdf for _master_marketplace_payments.pdf when the Marketplace template is selected, _master_game.pdf when the Game template is selected, or _master_data_analytics_pm.pdf when the Data AI template is selected.
If cp fails with Operation not permitted, try Python shutil.copy2 from the agent (often succeeds when /bin/cp does not). Avoid Chrome --print-to-pdf on a file:// PDF master — it produces a tiny invalid CV. Chrome is only for HTML → PDF (cover letters) unless every copy path has truly failed.
Filename convention: Ray Liu Resume-Company-ShortRole.pdf (hyphens between words). Do NOT append the LinkedIn Job ID to the filename. If two jobs from the same company would produce the same slug, differentiate by adding a word from the role title (e.g. Ray Liu Resume-eBay-Sr-PM-Marketplace.pdf vs Ray Liu Resume-eBay-Sr-PM-Buyer-Science.pdf), never by appending the numeric Job ID.
Output must be a materialized PDF (see STEP E): not a symlink; verify with test ! -L AND stat -f '%z' "<dest>" ≥ 150,000 bytes and (when copied from master) matching master byte size. Anything around 14–20 KB or ~100–130 KB is invalid — delete and fix the approach.
J. Shell: Write CL HTML to temp file
cat > /tmp/cl_company.html << 'CLEOF'
FULL HTML CONTENT FROM SECTION 7
CLEOF
echo "CL HTML written"
MUST use single-quoted 'CLEOF' to prevent shell variable expansion.
K. Shell: Chrome headless PDF
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless --print-to-pdf="FULL_OUTPUT_PATH" --no-margins /tmp/cl_company.html 2>&1 | tail -3
Verify: exit code 0 and output contains "bytes written to file".
Output filename convention: Ray Liu CL-Company-ShortRole.pdf. Do NOT append the LinkedIn Job ID. Use the same slug as the Resume file (matching Ray Liu Resume-Company-ShortRole.pdf).
L. TodoWrite — Track batch progress
Create 3 phases at the start:
- Phase 1: Fetch all JDs + duplicate checks (parallel) → in_progress
- Phase 2: Process each job sequentially (Stage 1 + Stage 2) → pending
- Phase 3: Final summary report → pending Update status as each phase completes.
12. EDGE CASES AND SOLUTIONS
| Situation | Solution |
|---|---|
| WebFetch returns 404 | Skip this job. Note "Listing removed" in final summary. |
Should apply validation error |
You used "__YES__" or "__NO__". Fix: use "Yes", "Maybe", or "No". |
notion-fetch fails with "invalid_type" on id |
You used url parameter. Fix: use id parameter instead. |
| Content update old_str not found | You didn't fetch the page first. Always fetch before update_content to get exact text. |
| Chrome PDF generation fails | Check HTML file exists at /tmp/cl_*.html. Retry once. If still fails, note in summary. |
| Resume PDF is symlink / Finder Kind Alias (tiny bytes) | rm the bad file. Never use ln -s. Copy master to /tmp/_master_cv.pdf, then cp into YYYY-MM-DD/. Verify test ! -L on the destination before continuing. |
cp of Drive _master_*.pdf returns Operation not permitted |
macOS TCC blocks shell reads of the Drive folder. Never use the Drive master as a read source. Resolve master via Section 2 priority (env CHRONA_CV_MASTERS_DIR → <job-lib>/assets/cv-masters/). If neither exists, STOP the batch with a precondition error and ask the user to seed masters. |
Generated Ray Liu Resume-*.pdf is ~100–130 KB instead of ~180–190 KB |
It is Chrome's ERR_ACCESS_DENIED page rendered to PDF — Chrome was pointed at an unreadable (Drive-TCC) master. Delete the file, confirm a readable master exists at `<job- |
…(truncated)