career-ops auto-pipeline 模式全解析:从 JD 抓取到 Tracker 入册的六步自动化求职流水线
当你向 career-ops 粘贴一段 JD(职位描述)文本或一个职位 URL 而没有显式指定子命令时,系统会自动执行 auto-pipeline 模式——一条覆盖「提取 JD → 存活校验 → 黑名单校验 → A-G 全量评估 → 报告落盘 → CV PDF 生成 → 申请表草稿 → Tracker 入册」的端到端流水线。本文以 modes/auto-pipeline.md 为主体,逐步拆解这条流水线的每一步触发条件、判断标准与配置依赖,并结合仓库中的 browser-extract.mjs、reserve-report-num.mjs、config/profile.example.yml 等源码,说明其背后的实现机制与容错设计,帮助你理解这条流水线「为什么这样设计」以及如何让它稳定运行在自己的环境里。
流水线总览:六步串行、一步失败不中断
modes/README.md 的 Mode 目录把 auto-pipeline 定义为「on a pasted JD/URL 的完整自动流水线(evaluate + PDF + tracker)」,而 AGENTS.md 的 Skill Modes 路由表给出了它的触发条件:"Pastes JD or URL" → auto-pipeline。也就是说,它是 career-ops 的默认入口——用户不需要记住任何子命令,粘贴即可。
完整步骤序列如下:
| 步骤 | 名称 | 核心动作 | 中止条件 |
|---|---|---|---|
| Step 0 | Extract JD | 从 URL 或粘贴文本中获取 JD 正文 | 所有提取手段失败 → 请用户手动粘贴 JD 或截图 |
| Step 0.5 | Liveness gate | 判定职位是否仍然在线 | 判定为已关闭 → 终止,不跑 Step 1–4 |
| Step 0.6 | Blacklist gate | 对照 data/blacklist.md 校验公司 |
命中且用户未确认 → 终止 |
| Step 1 | A-G Evaluation | 完整执行 oferta 模式的 A-G 七个区块 |
— |
| Step 2 | Save Report | 将评估写入 reports/{###}-{company-slug}-{YYYY-MM-DD}.md |
— |
| Step 3 | Generate PDF | 按 cv.output_format 分流到 latex / text / pdf 模式 |
— |
| Step 4 | Draft Application Answers | 仅当总分 >= 4.5 时生成申请表回答草稿 | — |
| Step 5 | Update Tracker | 在 data/applications.md 登记,Report 与 PDF 列均标 ✅ |
— |
modes/auto-pipeline.md 末尾给出了整条流水线的容错原则:"If any step fails, continue with the next ones and mark the failed step as pending in the tracker." 单步失败不会导致整条流水线回滚,失败步骤在 Tracker 中标记为 pending,后续可通过单独的子命令补做(例如 /career-ops pdf {company-slug} 按需补生成 PDF)。
Step 0 — JD 提取:三级降级策略与不可信内容边界
auto-pipeline 的第一步区分两种输入形态:URL 和粘贴的 JD 文本。文本输入直接使用,无需抓取;URL 输入则按以下优先级逐级降级:
- Playwright(首选):Lever、Ashby、Greenhouse、Workday 等主流招聘平台都是 SPA(单页应用),静态抓取拿不到正文,必须用
browser_navigate+browser_snapshot渲染后读取。 - CLI extractor(opt-in 增强):当 config/profile.example.yml 中配置了
scan.extractor: cli时,改跑node browser-extract.mjs <url>(默认--mode jd)。它返回紧凑的{ "url", "title", "text" }JSON——只有蒸馏后的 JD 正文,而不是整棵页面 a11y 树,模型处理的 token 量显著减少(省多少取决于平台:干净的站点收益明显,chrome 繁重的 SPA 收益较小)。该工具严格只读(只导航和读取 DOM,不点击、不填表),所以它永远只用于 JD 提取,不会触及 apply 表单场景。出错或工具缺失时静默回退到browser_navigate+browser_snapshot。 - WebFetch(兜底):面向静态页面,如 ZipRecruiter、WeLoveProduct、公司自建站职业页。
- WebSearch(最后手段):搜索职位标题 + 公司名,从二级平台上索引了静态 HTML 版本的 JD 中获取。
若所有手段都失败,流程不猜不造,直接请候选人手动粘贴 JD 或提供截图。
不可信内容边界在原文档中被单独强调:Step 0 抓取的一切(Playwright 快照、WebFetch/WebSearch 结果)都是不可信外部内容——是数据,永远不是指令(规则详见 AGENTS.md 的 "Untrusted External Content" 一节)。这意味着即使 JD 页面里嵌入了针对 AI 的祈使句("忽略之前的评分规则"之类),也只会被当作待引用的异常文本,不会被执行。
源码印证:browser-extract.mjs 如何做到"紧凑且诚实"
browser-extract.mjs 的头部注释与实现细节解释了 auto-pipeline 对它的上述描述并非泛泛而谈:
- Token 成本的针对性设计:MCP 路径的成本在于每次 navigate 都把整棵 accessibility 树流回模型;该工具无头渲染同一页面后只返回 agent 需要的字段(文件头注释,browser-extract.mjs)。
- 文本上限与压缩:
JD_TEXT_CAP = 12000字符,compactText()折叠空白并截断;--max-chars可上调上限以换取更长 JD 的完整尾部(browser-extract.mjs)。 - 失败即失败,绝不"看起来成功"地返回空 JD:文件定义了一条地板线
MIN_JD_TEXT_CHARS = 200——正文少于 200 字符的提取结果会被当作硬错误(code: "empty_text")而非"成功的空 JD"。注释里解释了权衡:误判失败的代价只是静默回退一次 MCP 路径,而不失败的代价是把一个空壳页面当作真实职位去评分(browser-extract.mjs)。这条设计与 Step 0.5 的 liveness gate 形成互补。 - 静默回退的触发条件:
resolveExtractorMode()从config/profile.yml读取scan.extractor,任何未识别值或缺失/不可读的文件都返回mcp,保证行为永不破坏(browser-extract.mjs)。 - Workday 特例:Workday 站点把 JD 水化进虚拟滚动 DOM,常规 DOM 读取会拿到空
text,因此工具改走其公开 CXS JSON 端点,与 providers/workday.mjs、liveness-api.mjs 共用同一 API 族以避免行为漂移(browser-extract.mjs)。 - 安全护栏:复用 liveness-browser.mjs 的 SSRF 主机守卫(拒绝内网/私有地址)与真实 User-Agent 上下文,避免被反爬墙直接拦截。
对应地,config/profile.example.yml 的注释给出了两个取值的行为对比:mcp(默认)开箱即用但快照 token 重;cli 是 opt-in,可将每页 token 削减约 2–5 倍,且 doctor 会报告当前生效的模式。
Step 0.5 — Liveness Gate:花 token 之前先确认职位还活着
原文档给出的动机很直接:Step 0 的 Playwright 快照里已经包含了判定证据,应该在消耗 A-G 评估、报告、PDF 的 token 之前就判定完毕——否则一个 404/过期页面会以"静态兜底内容"("position filled"、空壳页)的形式被完整地评一遍分。
判定分类标准是二分的:
- active posting evidence(在线证据):标题/职位名 + 真实职位描述,或存在申请入口(apply path);
- closed posting evidence(关闭证据):expired/closed/"no longer accepting applications"、只有导航和页脚而 JD 缺失、硬跳转到通用招聘/搜索页、或 404/410。
行为规则:
- 判定为关闭或死壳 → 立即停止,不执行 Step 1–4,告知用户链接已死;若该条目来自
data/pipeline.md收件箱,则将其标记为- [x] ~~Company | Role~~ — oferta nieaktywna(已完成的删除线条目)。 - 若用户只粘贴了 JD 文本(没有 URL),没有可验证的链接——跳过该 gate,直接继续。
- "Do not continue to Step 1 until this gate is resolved."——gate 未解决前不允许进入评估。
从源码结构看,这个 gate 与 modes/oferta.md 的 "Liveness gate" 是同一套语义:oferta 模式要求直接 URL 入口时自行导航并复用同一分类标准,而从 auto-pipeline 进入 oferta 时则复用 Step 0.5 已捕获的快照,不重复导航。同一快照还会被 Block G 的 freshness 信号复用,一次抓取服务多处判断。
Step 0.6 — Blacklist Gate:用户自己的"不予申请"清单(#1742)
若 data/blacklist.md 存在(用户层文件、opt-in;文件不存在 = 跳过此 gate),在跑任何评估之前先对照检查目标公司。匹配规则是大小写和标点不敏感的——清单里的 "Acme Corp." 能命中 JD 中写 "acme corp" 的职位(模板见 templates/blacklist.example.md)。
命中时的行为是原文档中最体现 HITL(human-in-the-loop)精神的段落:
- 在 Step 1 之前停止,并复述用户自己记录的决策:告诉用户命中了哪一条、引用当时记录的Reason,话术为:
"{Company} is on your blacklist (since {Since}): *{Reason}*. Do you still want me to evaluate it?" - 等待明确回答——既不静默拒绝,也不静默继续。用户的决定永远胜出(与 score < 4.0 规则的 HITL 精神一致):明确的 yes 正常进入 Step 1;其他任何回答都在此终止流水线,且若条目来自
data/pipeline.md,标记为- [x] ~~Company | Role~~ — blacklisted。 - 黑名单条目永远不会改变任何分数——它是闸门(gate),不是信号(signal),与 Block G 的合法性评估完全正交。
modes/oferta.md 中的 "Blacklist gate" 一节携带同一编号 #1742,两条模式共享同一套判定与话术,保证无论走单评入口还是自动流水线,黑名单的行为完全一致。
Step 1 — A-G 评估:继承 oferta 的完整区块与受限研究预算
Step 1 的规则只有一句话加两条补充:执行与 oferta 模式完全相同的评估(完整读取 modes/oferta.md 的 A-F 区块 + Block G 发布合法性),并在存在 modes/_custom.md 时应用其 Evaluation Rules 覆盖默认;默认行为为标准 A-G 评估。
modes/oferta.md 是这一步的完整实现规格,其核心结构包括:
- Block A(Role Summary):检测职位原型(archetype)、领域、职能、级别、远程政策、团队规模、Culture screen 结论,外加 Geo-mismatch 检查与 Work-authorization 四级判定(✅ Sponsors / ➖ Not needed / ⚠️ Unstated / ⛔ No sponsorship)。
- Block B(Match with CV):需求→证据映射表,强制"两遍生成"(Pass 1 只读 JD 填 Requirement/Importance,Pass 2 读 cv.md 填 Match/Evidence),Importance 采用五档枚举(critical/high/meaningful/preferred/low_signal)加三级证据分层(stated/structural/inferred),且有明确的 gate:
inferred行永远不能是 critical/high,也不得贡献 hard_stops——避免用市场猜测制造不存在的面试风险。 - Block C–F:级别策略、薪酬与需求(含公司类型分类表与薪酬可靠性分层)、CV 定制计划、面试计划(STAR+R 故事)。
- Block G(Posting Legitimacy):15 项信号检查(发布新鲜度、JD 质量、公司招聘信号、重发检测、雇佣分类风险、薪酬区间宽度、最低工资律师问题、AI 筛查披露等),输出 High Confidence / Proceed with Caution / Suspicious 三档,且明确不影响 1–5 全局分。
auto-pipeline 在此之上附加了两条关键约束:
- 代理中介职位(#1596):若 JD 闻起来像猎头/代理机构发的("our client"、代理域名、未具名雇主),必须在写 Tracker 行之前询问用户是通过哪家代理。终端雇主记为
?(永远不写 "Confidential"——那是 locale 相关的词,会与真实公司名冲突),代理名进 Via 字段 / TSV 的via=标签,Notes 里放区分性描述(如fintech, Leeds)。完整约定见 modes/oferta.md 与 modes/tracker.md。 - 受限研究预算(bounded research budget):评估继承 oferta 的预算约束——公司与薪酬、招聘信号查询不得调用
deep-research、不得派生子 agent,必须在共享查询上限处停下(modes/oferta.md 给出的硬上限是 Block D + G 合计 5 条 WebSearch 查询),而不是升级为开放式调查。modes/_shared.md 的 "Subagent delegation (cost guardrail)" 把这条规则提升到全局:单个/career-ops <JD>评估只允许评估一个职位,绝不允许膨胀成自我复制的 agent 群——这是自动流水线在 token 经济上最关键的护栏。
评分体系本身(五维合并为 1–5 全局分、4.5+ 强烈建议申请、4.0–4.4 值得申请等解释)定义在 modes/_shared.md,Step 4 的 4.5 门槛与 config/profile.example.yml 的 auto_pdf_score_threshold(pipeline 批量场景的 Auto-PDF 阈值,默认 3.0)是两个不同层面的门槛:前者针对单条 auto-pipeline 是否值得草拟申请表回答,后者针对批量扫描中哪些职位值得花 30–60 秒渲染 PDF。
Step 2 — 报告落盘:原子编号与固定文件命名
评估全文保存到 reports/{###}-{company-slug}-{YYYY-MM-DD}.md,格式继承 modes/oferta.md 的 "Post-evaluation" 规格:
{###}必须原子分配:运行node reserve-report-num.mjs获取编号(stdout 返回三位零填充编号),写完报告后运行node reserve-report-num.mjs --release {###}释放哨兵。reserve-report-num.mjs 的实现证实了这一要求的严肃性:编号保留同时覆盖报告文件、保留哨兵、Tracker 行 ID 与 Tracker 中的报告链接;Tracker 扫描与哨兵创建在同一把 Tracker 锁下执行,跨进程原子性由O_CREAT|O_EXCL保证(reserve-report-num.mjs)。这是批量/并行场景下防止编号竞争的工程基础。{company-slug}:公司名小写、空格转连字符;代理中介且终端雇主未知时用confidential-{agency-slug}(如042-confidential-hays-2026-07-06.md),且文件永不重命名——雇主揭晓时只更新标题/头部/YAML(modes/oferta.md)。- 报告头部必须包含
**URL:**与**Legitimacy:**两行——auto-pipeline 相对纯 oferta 的增量要求,把合法性档位从 Block G 正文提升到头部,方便快速扫读。 - 报告正文结构为 Machine Summary(YAML fence,供下游脚本消费,schema 以 batch/batch-prompt.md 为准)→ A–G 七区块 → Risk Summary → 条件性的 H 区块(见下节)。
Step 3 — PDF 生成:由 cv.output_format 三路分流
Step 3 读取 config/profile.yml 的 cv.output_format 并按值分流(config/profile.example.yml 给出了该键的注释与取值说明):
cv.output_format 取值 |
执行 |
|---|---|
"latex" |
完整执行 modes/latex.md 流水线 |
"text" |
完整执行 modes/text.md(定制 markdown CV,不产 PDF) |
| 其他 / 默认 | 完整执行 modes/pdf.md(ATS 优化 PDF) |
注意 modes/latex-tex.md 提到的 opt-in:用户自有的 LaTeX CV(cv.latex.source)服务于 /career-ops latex-tex,而 cv.md 始终是评估与 auto-pipeline 的默认真相源——分流改变的是"定制 CV 的产出形态",不改变评估输入。
Step 4 — 申请表回答草稿:只有高分职位才配得上这一步
触发条件是最终分 >= 4.5(与 modes/_shared.md 的 "4.5+ → Strong match, recommend applying immediately" 档位对齐——只有"立即申请"级别的职位才值得预先写好表单答案)。流程三步:
- 提取表单问题:用 Playwright 导航到申请表并快照;若提取不出表单问题,回退到通用问题集。
- 按语气规范生成回答。
- 保存进报告,作为
## H) Draft Application Answers小节——与 modes/oferta.md 报告模板中 "only if score >= 4.5" 的 H 区块定义一致,auto-pipeline 只是把它真正写入了。
通用问题集(表单问题提取失败时使用):
- Why are you interested in this role?
- Why do you want to work at [Company]?
- Tell us about a relevant project or achievement
- What makes you a good fit for this position?
- How did you hear about this role?
**语气规范(Tone for Form Answers)**是原文档中最有实战价值的部分,完整继承如下:
- 站位:"I'm choosing you." 候选人手中有选项,是出于具体原因选择这家公司。
- 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"。
- Selective without arrogance:如 "I've been intentional about finding a team where I can contribute meaningfully from day one"。
- Specific and concrete:每个回答都引用 JD 或公司的一个真实细节 + 候选人经历的真实细节。
- Direct, without fluff:每题 2–4 句;禁用 "I'm passionate about..." / "I would love the opportunity to..." 之类的空话。
- 钩子是证据,不是陈述:不说 "I'm great at X",说 "I built X that does Y"。
每题的框架:
| 问题类型 | 框架 |
|---|---|
| Why this role? | "Your [specific thing] maps directly to [specific thing I built]." |
| Why this company? | 说一件关于该公司的具体事:"I've been using [product] for [time/purpose]." |
| Relevant experience? | 一个量化的证明点:"Built [X] that [metric]. Sold the company in 2025." |
| Good fit? | "I sit at the intersection of [A] and [B], which is exactly where this role lives." |
| How did you hear? | 诚实回答:"Found through [portal/scan], evaluated against my criteria, and it scored highest." |
语言规则:始终使用 JD 的语言(默认英文),并应用 /tech-translate。所有生成文案同时受 modes/_writing.md 的专业写作规则约束(无 em dash、无 buzzword、主动语态、只写具体声明),且事实边界受 modes/_shared.md 的 Sources of Truth 规则约束——指标必须来自 cv.md / article-digest.md,不得虚构。
Step 5 — Tracker 入册:全列登记与失败降级
最终步骤是把条目记入 data/applications.md,所有列齐全,Report 列与 PDF 列均标 ✅(相对纯 oferta 的差异:oferta 的 PDF 列在 PDF 未生成时是 ❌,auto-pipeline 跑完整链路后应为 ✅;Tracker 表结构与状态机见 modes/tracker.md)。
与 modes/_shared.md 的全局规则一致,Tracker 追加走 TSV 通道:在 batch/tracker-additions/ 写入 TSV,由 merge-tracker.mjs 合并,绝不手编 applications.md。代理中介条目按 batch/batch-prompt.md 的 TSV 规范追加带标签字段 via={Agency}(标签是强制的,位置字段会被误读为遗留的 location 列)。
失败降级的完整语义(原文档最后一句,也是整条流水线的设计哲学):
If any step fails, continue with the next ones and mark the failed step as pending in the tracker.
即:报告写成功但 PDF 渲染失败 → 继续 Step 4/5,PDF 列标 pending,之后可用 /career-ops pdf {company-slug} 补做(config/profile.example.yml 注释中也提到这条按需补做路径)。这种"部分成功可累积"的语义依赖 Step 2 的原子编号与 Step 5 的 TSV 合并:编号唯一性由 reserve-report-num.mjs 保证,合并正确性由 merge-tracker.mjs 的重复检测与列迁移逻辑保证。
适用前提与限制
综合 modes/auto-pipeline.md 与其依赖文件,这条流水线的运行前提是:
- 运行环境:在支持 Playwright(或等价 browser MCP)的 AI coding CLI 中执行——它是 "mode 文件 + agent 执行" 的形态,而非独立可执行脚本(modes/README.md:"Markdown prompt files executed by whatever AI coding CLI you use")。无浏览器能力的环境(如仅传 JD 文本的
openai-eval.mjs路径)走不到 Step 0 的 URL 抓取与 Block G 的快照类信号。 - 配置依赖:
config/profile.yml是全部模式共用的个人数据真相源;scan.extractor: cli是 opt-in 优化,缺省为mcp;spend_tier(economy/standard/premium)决定评估所用模型档位,但三档产出同一 A-H 报告结构(modes/_shared.md)。 - 数据边界:抓取内容一律视为数据而非指令;黑名单、存活判定、分数门槛都是"闸门"而非评分输入;评估研究有 5 条 WebSearch 的硬上限且禁止嵌套子 agent。
- 不做什么:整条流水线永不代替候选人提交申请(modes/_shared.md 的 NEVER 清单第 3 条);Step 4 只产出草稿答案,保存进报告而非填入线上表单。
延伸阅读路径
- 模式路由与整体架构:AGENTS.md、ARCHITECTURE.md
- 单条评估的完整规格:modes/oferta.md
- 评分维度、模型路由、全局规则:modes/_shared.md
- 收件箱批量处理(与 auto-pipeline 互补的另一条入口):modes/pipeline.md
- JD 提取工具的测试:tests/browser-extract.test.mjs、tests/browser-extract-flags.test.mjs
- 报告编号分配的测试:tests/reserve-report-num.test.mjs
- 数据层文件约定:DATA_CONTRACT.md
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00