career-ops Skill 路由器解析:SKILL.md 如何把一个 AI 编码 CLI 变成求职指挥中心
本篇技术文章以 career-ops 仓库中的 Skill 入口文件 SKILL.md 为核心,完整拆解这个"路由器"(Router)的设计:它是如何被各 AI 编码 CLI(Claude Code、Codex、OpenCode、Cursor、Kimi 等)加载的,如何通过"哨兵文件"解析项目根目录、如何把用户输入(模式名或一段 JD 文本)分发到 modes/ 目录下的 30 多个执行模式,以及如何在执行前注入输出语言指令并按模式分级加载上下文。读完本文,你可以理解"一份自然语言路由文档 + 目录约定"是如何替代硬编码 CLI 的,以及如何为多 CLI 项目设计可测试的 Skill 入口。
1. Skill 的注册方式:YAML Frontmatter 即接口
文件开头的 YAML frontmatter 是 Skill 的注册元数据,定义了名称、触发条件、参数与调用方式:
name: career-ops
description: >-
AI job search command center -- evaluate offers, generate CVs, scan portals,
track applications. Use when the user pastes a job URL or JD, asks to scan
portals, generate a CV/PDF, track applications, prepare for interviews, draft
outreach/emails, or run any career-ops mode.
arguments: mode
user_invocable: true
user-invocable: true
argument-hint: "[scan | discover | deep | pdf | text | latex | cover | email | add | ...]"
license: MIT
几个字段的作用值得注意:
description同时面向人和模型:它用自然语言描述"何时应该触发这个 Skill"(粘贴 JD/URL、要求扫门户、生成 CV/PDF、追踪申请等),是 Agent 决定调用的依据。arguments: mode声明唯一的入参。文档正文所有路由逻辑都围绕"从$mode确定模式"展开。argument-hint列出约 38 个合法取值,起到补全提示与文档自描述的双重作用。user_invocable: true(以连字符/下划线两种键名同时声明)表示该 Skill 可被用户直接以/career-ops调用,而不只是被其他 Agent 内部调用。
需要强调的是路径本身:本文件位于 .kimi/skills/career-ops/SKILL.md,但它并不是唯一副本。从 scaffolder/bin/skill-entrypoints.mjs 的 CANONICAL_SKILL_PATH 常量可以看出,仓库的规范路径(canonical)是 .agents/skills/career-ops/SKILL.md,而 .claude/、.cursor/、.opencode/、.qwen/、.antigravitycli/、.grok/、.kimi/ 七条入口路径默认保存的是指向该规范文件的一行指针(../../../.agents/skills/career-ops/SKILL.md,即符号链接);在不支持符号链接的文件系统上,materializeSkillEntrypoints() 会在 npx init 或系统更新时把指针物化为规范内容的完整拷贝。也就是说,所有 CLI 加载的其实是同一份路由器文档——这正是文档中"routing below is shared across supported agent CLIs even when the invocation surface differs"这句声明的仓库级实现基础。各 CLI 自身的配置文件(如 KIMI.md 通过 @AGENTS.md 导入主指令)只负责把 Agent 引到仓库约定,路由语义全部集中在这一份 SKILL.md 里。
2. 项目根目录解析:两个哨兵文件 + 向上遍历
路由器要读取 modes/、config/、data/、脚本与模板等大量相对路径,但 AI CLI 的进程当前工作目录(CWD)不可靠——用户可能从嵌套检出目录(如 Development\career-ops)启动,或从子目录运行命令。文档为此规定了一套确定性的根目录推导算法:
从已加载的 SKILL.md 所在目录开始向上遍历,直到找到同时包含
AGENTS.md和modes/的最近目录,该目录即PROJECT_ROOT;路由器内的所有路径(modes/、config/、data/、脚本、模板、输出路径)一律相对PROJECT_ROOT解析,绝不相对进程 CWD 解析。若两个哨兵文件都找不到,则停止操作,先定位 career-ops 检出位置,再继续读写文件。
这不是"文档建议",而是被 CI 测试强约束的行为契约。tests/skill-project-root.test.mjs 对全部 8 条 CLI 入口(.agents、.antigravitycli、.claude、.cursor、.grok、.kimi、.opencode、.qwen 下的 skills/career-ops/SKILL.md)逐一验证了两点:
- 测试内实现的
findProjectRoot()用与文档相同的"向上找AGENTS.md+modes/"算法,断言每条入口解析出的根都等于仓库根; - 每个入口(包括经 Git 索引模式
120000即符号链接解析后的目标)的文本必须包含路由规则关键字句 "Resolve every path in this router ... never against the process's current working directory"(hasRoutingRule()检查)。测试还构造了一个临时 Git 仓库 fixture,验证"物化的符号链接指针"也能正确解析到规范内容。
从源码结构看,这种"哨兵文件对 + 向上遍历"是一种很经济的根目录定位方案:不依赖环境变量的存在、不依赖启动目录,且把正确性写进了可自动运行的测试,防止某次编辑把路由规则从文档里删掉。
与之互补的是另一套**数据根(Data Root)**解析:用户个人数据(cv.md、config/profile.yml、追踪表等)不一定放在仓库根。modes/_shared.md 规定其优先级为:环境变量 CAREER_OPS_ROOT / CAREER_OPS_DATA_DIR → 仓库根的 .career-ops-data 标记文件 → 仓库根兜底;追踪表路径还可由 CAREER_OPS_TRACKER 覆盖,规范写入位置为 {DATA_ROOT}/data/applications.md。两层根目录各管各的:PROJECT_ROOT 用于定位系统层文件(模式文档、脚本、模板),DATA_ROOT 用于定位用户层文件,二者在 AGENTS.md 的 Data Contract 中被正式分层——用户层永不自动更新,系统层(含 SKILL.md 所路由的全部 modes/ 文档与 *.mjs 脚本)可自动更新。
3. 调用方式:斜杠命令与自然语言提示词的同构映射
SKILL.md 明确区分了两类入口面(invocation surface):
- 支持斜杠命令注册的 CLI:路由器暴露为
/career-ops,参数即$mode。 - Cursor:Skill 位于
.cursor/skills/career-ops/下被自动发现;用户可以按名称要一个模式,或直接粘贴 JD/URL 触发 auto-pipeline。 - Codex:交互式会话在仓库根使用
codex,无头工作用codex exec "prompt"。由于 Codex 不保证斜杠命令,文档给出与斜杠命令语义等价的自然语言提示词模板:
Evaluate this JD with career-ops auto-pipeline: https://company.com/jobs/123
Run the career-ops scan mode and summarize new matches.
Run the career-ops pipeline mode for data/pipeline.md.
Run the career-ops pdf mode for the latest evaluated role.
Run the career-ops tracker mode and summarize the current statuses.
文档进一步给出了斜杠调用与 Codex 提示词的一一对应表(/career-ops {JD} ↔ "Evaluate this JD with career-ops auto-pipeline: {JD}",/career-ops scan ↔ "Run the career-ops scan mode..." 等),并声明"无论入口是斜杠命令还是自然语言提示,下文路由语义保持不变"。设计意图清晰:路由逻辑是入口无关的纯函数,斜杠只是语法糖,自然语言提示词与斜杠参数走同一张路由表。
4. 模式路由表:从 $mode 到 modes/*.md
路由的核心是一张"输入 → 模式"映射表(以下为文档原文的完整继承):
| 输入 | 模式 |
|---|---|
| (空 / 无参数) | discovery —— 显示命令菜单 |
| JD 文本或 URL(无子命令) | auto-pipeline |
oferta |
oferta |
ofertas |
ofertas |
contacto |
contacto |
deep |
deep |
interview-prep |
interview-prep |
interview |
interview |
eu-swe |
regional/eu-swe |
eu-fintech |
regional/eu-fintech |
interview/plan |
interview/plan |
interview/practice |
interview/practice |
interview/debrief |
interview/debrief |
pdf |
pdf |
text |
text |
latex |
latex |
latex-tex |
latex-tex |
email |
email |
add |
add |
expand |
expand |
training |
training |
project |
project |
tracker |
tracker |
agent-inbox |
agent-inbox |
inbox |
agent-inbox |
pipeline |
pipeline |
apply |
apply |
scan |
scan |
discover |
discover |
batch |
batch |
patterns |
patterns |
offer-prep |
offer-prep |
titles |
titles |
upskill |
upskill |
followup |
followup |
reply-watch |
reply-watch |
outcome |
outcome |
interview-redflag |
interview-redflag |
update |
update |
cover |
cover |
从表中可以读出几条路由规则的细节:
- 别名折叠:
inbox映射到agent-inbox,eu-swe/eu-fintech映射到regional/子目录下的地区模式——用户输入的是短别名,模式文件在子目录中。 - 子命令式命名:
interview/plan、interview/practice、interview/debrief用斜杠表达"父模式 + 子动作",对应modes/interview/子目录中的文档,而裸interview是交互式画像/简历引导。 - 落点文件约定:模式名即
modes/{mode}.md文件名,例如oferta→ modes/oferta.md、pdf→ modes/pdf.md、scan→ modes/scan.md、pipeline→ modes/pipeline.md、tracker→ modes/tracker.md、cover→ modes/cover.md。
Auto-pipeline 自动检测是路由表之外的兜底规则:若 $mode 不是已知子命令,且其内容包含 JD 特征——关键词("responsibilities"、"requirements"、"qualifications"、"about the role"、"we're looking for"、公司名+职位名)或一个 JD URL——则执行 auto-pipeline(评估 + 报告 + PDF + 登记追踪表的全流水线);若既非子命令也不像 JD,则回落到 discovery 显示菜单。这一条把"用户直接粘贴一段招聘启事"这一最高频操作映射到了最重的流水线,是文档中唯一的隐式路由。
5. 输出语言指令:路由之后、执行之前的统一注入点
在确定模式之后、产生任何用户可见内容之前,路由器要求读取 config/profile.yml(若存在)并解析两个键:
language.output→ 人类可读输出的 ISO 语言码,默认en。language.modes_dir→ 可选的市场模式目录(如modes/de),只控制市场词汇与本地评估规则,不控制行文语言。
随后在"加载完模式指令之后、生成任何用户可见内容之前"注入如下指令:
Write all human-facing output in
{language.output}regardless of the language of these instructions or of the job description. This includes reports, tracker notes, PDFs, cover letters, outreach, interview prep, form answers, and summaries. Iflanguage.modes_dirsupplies market-specific vocabulary, keep the market logic but explain terms in{language.output}when needed.
文档特别强调一条权威序关系:language.output 对行文语言是权威的;modes_dir 只是市场上下文,不得强制行文语言。这在多语言仓库中很关键——modes/ 目录下存在 de/、fr/、es/、ja/、zh/、ko/ 等约二十个市场模式子目录(如 modes/de/angebot.md、modes/pt/ 等),它们提供的是各市场的求职词汇与评估规则;而用户希望报告用什么语言写,只由 output 一个键决定。配置侧的说明与之完全对应,见 config/profile.example.yml:
language:
# Human-facing output language for reports, tracker notes, PDFs, cover
# letters, outreach, and form answers. Use an ISO language code such as
# `en` or `zh-CN`; `zh-CN` also enables Chinese PDF typography rules.
# This is separate from language.modes_dir: modes_dir selects market
# vocabulary/rules, while output selects the prose language.
output: en
# modes_dir: modes/de # optional: use DACH market vocabulary while still writing in English
值得注意的是该示例文件里 modes_dir 一行默认是注释掉的("可选"),即开箱即用的行为是英文行文 + 默认市场规则;只有显式配置后路由器才会切换到某市场目录。
6. Discovery 模式:无参数时的完整命令菜单
当用户不带任何参数调用(或输入无法路由)时,discovery 模式输出完整的命令菜单。该菜单就是用户可获得的"命令行帮助",其全部条目(继承自 SKILL.md 原文)如下:
career-ops -- Command Center
Available commands:
/career-ops {JD} → AUTO-PIPELINE: evaluate + report + PDF + tracker (paste text or URL)
/career-ops pipeline → Process pending URLs from inbox (data/pipeline.md)
/career-ops oferta → Evaluation only A-F (no auto PDF)
/career-ops ofertas → Compare and rank multiple offers
/career-ops contacto → LinkedIn power move: find contacts + draft message
/career-ops deep → Deep research prompt about company
/career-ops interview-prep → Generate company-specific interview prep doc
/career-ops interview → Interactive profile/CV onboarding interview
/career-ops eu-swe → Calibrate a European SWE application before CV/apply/interview
/career-ops eu-fintech → Scan 21 EU fintech portals for Product Manager roles (zero-token)
/career-ops interview/plan → Time-blocked prep plan for an upcoming interview
/career-ops interview/practice → Practice interview, one question at a time with feedback
/career-ops interview/debrief → Post-interview debrief: close gaps, predict next round
/career-ops pdf → PDF only, ATS-optimized CV
/career-ops text → Tailored markdown CV (mirrors cv.md, no PDF)
/career-ops latex → Export CV as LaTeX/Overleaf .tex
/career-ops latex-tex → Tailor your own resume.tex in place (opt-in; cv.md stays default)
/career-ops cover → Cover letter: standalone JD paste or /career-ops cover {slug}
/career-ops email → Formal application email draft (draft-only; never sends, submits, or clicks)
/career-ops add → Add a project/paper/role to your CV (fetch + preview + confirm)
/career-ops expand → Auto-discover and add missing competencies from profile links
/career-ops training → Evaluate course/cert against North Star
/career-ops project → Evaluate portfolio project idea
/career-ops tracker → Application status overview
/career-ops agent-inbox → Queue/drain requests for the next session (data/agent-inbox.md)
/career-ops apply → Live application assistant (reads form + generates answers)
/career-ops scan → Scan portals and discover new offers
/career-ops discover → Resolve a company list to scannable ATS boards + append to portals.yml (zero-token)
/career-ops batch → Batch processing with parallel workers
/career-ops patterns → Analyze rejection patterns and improve targeting
/career-ops offer-prep → Read a received offer/contract with the candidate: clause walk + lawyer questions (not legal advice)
/career-ops titles → Suggest adjacent job titles from your CV to broaden the search
/career-ops upskill → Aggregate skill-gap analysis from your evaluated reports
/career-ops followup → Follow-up cadence tracker: flag overdue, generate drafts
/career-ops outcome → Record application outcome & archive artifacts
/career-ops update → Update career-ops system files with diff preview + compat check
Inbox: add URLs to data/pipeline.md → /career-ops pipeline
Or paste a JD directly to run the full pipeline.
菜单末尾同时给出了"收件箱"用法:把 URL 追加到 data/pipeline.md 再运行 pipeline 模式批量处理。文档还要求在不支持斜杠命令的 Codex 场景下,以纯文本列出同样选项并按同一映射解析用户请求。
从功能分组看,这份菜单覆盖了求职流水线的完整生命周期:发现(scan/discover/batch/patterns)→ 评估({JD} auto-pipeline、oferta/ofertas/deep)→ 投递准备(pdf/text/latex/latex-tex/cover/email/add/expand)→ 投递与跟进(apply/pipeline/agent-inbox/followup/reply-watch)→ 面试(interview* 家族、interview-prep)→ 结果与复盘(outcome/patterns/offer-prep/upskill)→ 系统维护(update)。每个条目的描述本身就带有安全边界声明,如 email 标注 "draft-only; never sends, submits, or clicks"、offer-prep 标注 "not legal advice"——这些免责/边界语句直接内嵌在路由层,先于各模式文档生效。
7. 按模式加载上下文:三级文件加载顺序与子代理委派
路由确定后,文档定义了"执行前必须加载哪些文件"的分级规则。全部规则继承自 SKILL.md 原文:
公共规则:若 modes/_custom.md 存在,在 modes/_profile.md 之后、选定模式文件之前读取。它承载用户自定义的流程/风格规则,可以覆盖工作流默认值,但永远不引入关于候选人的事实性声明。
第一级:需要 _shared.md + 模式文件。加载顺序为 modes/_shared.md + modes/_profile.md(若存在)+ modes/_custom.md(若存在)+ modes/{mode}.md。适用于:auto-pipeline、oferta、ofertas、pdf、text、contacto、apply、pipeline、scan、batch。
第二级:独立模式(带画像与自定义上下文)。加载顺序为 modes/_profile.md(若存在)+ modes/_custom.md(若存在)+ modes/{mode}.md,不加载 _shared.md。适用于:tracker、agent-inbox、deep、interview-prep、interview、regional/eu-swe、interview/plan、interview/practice、interview/debrief、latex、latex-tex、training、project、patterns、titles、upskill、followup、reply-watch、outcome、cover、email、add、offer-prep、discover。
第三级:委派给子代理(worker/subagent)的模式。scan、apply(配合 Playwright)、pipeline(3 条以上 URL)应以 worker/subagent 形式启动,将 _shared.md + _profile.md(若存在)+ _custom.md(若存在)+ modes/{mode}.md 的内容整体注入 worker 提示词。若 CLI 暴露 Agent(...) 原语,调用形如:
Agent(
subagent_type="general-purpose",
prompt="[output language directive]\n\n[content of modes/_shared.md]\n\n[content of modes/_profile.md if exists]\n\n[content of modes/_custom.md if exists]\n\n[content of modes/{mode}.md]\n\n[invocation-specific data]",
description="career-ops {mode}"
)
最后一条指令是"执行加载到的模式文件中的指令"——即 SKILL.md 本身不含任何业务逻辑,它只是把正确的上下文文件按正确顺序拼装成提示词,然后让 Agent 执行目标模式文档。
从源码结构看,这种"文档即代码"的路由之所以可行,依赖三条仓库级支撑:
- 加载顺序在模式层被再次声明。modes/_shared.md 中 "RULE: Read _profile.md AFTER this file" 与 "Read _custom.md AFTER _profile.md" 与 SKILL.md 的顺序规定相互印证,且
_shared.md明确标注 "THIS FILE IS AUTO-UPDATABLE. Don't put personal data here"——系统层文件随版本更新,个人定制只能写在_profile.md/_custom.md(对应模板 modes/_profile.template.md、modes/_custom.template.md)。 - 子代理委派带有成本护栏。
_shared.md的 "Subagent delegation (cost guardrail)" 一节规定:任何为 career-ops 派生的子代理都是单次 worker——不得再派生子代理、不得调用其他 Skill(尤其开放式递归研究 Skill),公司与薪资研究必须内联完成。这是对"一个/career-ops <JD>绝不能膨胀成自复制 agent 群"的显式约束,与 SKILL.md 的Agent(...)调用形成"授权 + 限权"的配对设计。 - 加载的上下文决定评估行为。被第一级模式加载的
_shared.md同时携带评分体系(五维 → 1-5 全局分)、Spend Tier 模型路由表(config/profile.yml的spend_tier取值economy/standard/premium,缺省standard)以及 NEVER/ALWAYS 规则集。路由器虽然只负责"加载",但加载清单本身决定了每个模式能"看见"哪些系统规则——这也是把auto-pipeline/oferta等重评估模式归入第一级的原因。
8. 路由器文档与规范副本的同步机制:如何保证七份入口不漂移
最后一节回到工程实现:既然 .kimi/skills/career-ops/SKILL.md 等七条入口都是指向 .agents/skills/career-ops/SKILL.md 的符号链接或物化拷贝,如何防止"改了规范副本、旧拷贝没同步"?仓库给出了三层机制:
- 指针约定(scaffolder/bin/skill-entrypoints.mjs):
ensureSkillEntrypoints()在初始化时补齐缺失入口(写入指针文本../../../.agents/skills/career-ops/SKILL.md);materializeSkillEntrypoints()专门处理"文件系统不支持符号链接"的场景——当某入口是一个普通文件且内容恰等于指针字符串时,就地重写为规范内容。两个函数都以CANONICAL_SKILL_PATH为唯一内容源。 - 测试强约束(tests/skill-project-root.test.mjs):如前所述,它遍历全部 8 条入口,验证根目录解析结果等于仓库根、路由规则文本存在;并针对 Git 索引模式
120000(符号链接在 Windows 等平台上可能以"内容即目标路径"的普通文件形式检出)构造 fixture,验证readSkillEntrypoint()能通过索引模式把"指针文件"还原为真实目标内容再解析根目录。测试注释标明该行为对应 issue #3332。 - 系统更新复用同一入口集合:AGENTS.md 规定每个会话静默运行
node update-system.mjs check;scaffolder 的注释明确这套 bootstrap"用于 npx init 与 update-system",即版本更新时同样会重新执行入口补齐/物化,确保老版本检出新 CLI 入口(注释中的例子:v1.13.0 之前克隆的仓库补 Grok 入口)。
从源码结构看,可以推断这套设计的目标是**"一份路由逻辑,N 个 CLI 门面"**:路由规则的变更只发生在 .agents/ 下的规范文档一处,符号链接/物化机制负责传播,tests/skill-project-root.test.mjs 负责在每次变更时验证传播结果与关键语义(CWD 无关解析规则)未被破坏。对任何需要在多个 Agent CLI 中分发同一套行为规范的开源项目,这是一个可直接借鉴的模式:哨兵文件定位根目录、指针文件统一内容源、以测试锁定关键不变式。
9. 小结
SKILL.md 展示了"自然语言路由器"的完整工程形态:frontmatter 声明触发与参数,哨兵文件算法保证路径解析与 CWD 无关,一张 39 行的映射表把斜杠命令、自然语言提示词和 JD 粘贴三种输入统一分发到 modes/ 下的模式文档,language.output/modes_dir 双键分离行文语言与市场规则,三级上下文加载清单决定每个模式执行前的"视野",子代理委派在授权的同时被单-pass 护栏限权。所有关键不变式都有对应的测试(tests/skill-project-root.test.mjs)与工具链(scaffolder/bin/skill-entrypoints.mjs)兜底。对读者而言,这份文档既是 career-ops 使用方式的总目录,也是一份关于"如何在 AI 编码 CLI 生态中设计可移植 Skill 入口"的参考实现。
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 StartedRust0627
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