首页
/ career-ops Skill 路由器解析:SKILL.md 如何把一个 AI 编码 CLI 变成求职指挥中心

career-ops Skill 路由器解析:SKILL.md 如何把一个 AI 编码 CLI 变成求职指挥中心

2026-09-06 19:42:59作者:乔或婵

本篇技术文章以 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.mjsCANONICAL_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.mdmodes/ 的最近目录,该目录即 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)逐一验证了两点:

  1. 测试内实现的 findProjectRoot() 用与文档相同的"向上找 AGENTS.md + modes/"算法,断言每条入口解析出的根都等于仓库根;
  2. 每个入口(包括经 Git 索引模式 120000 即符号链接解析后的目标)的文本必须包含路由规则关键字句 "Resolve every path in this router ... never against the process's current working directory"(hasRoutingRule() 检查)。测试还构造了一个临时 Git 仓库 fixture,验证"物化的符号链接指针"也能正确解析到规范内容。

从源码结构看,这种"哨兵文件对 + 向上遍历"是一种很经济的根目录定位方案:不依赖环境变量的存在、不依赖启动目录,且把正确性写进了可自动运行的测试,防止某次编辑把路由规则从文档里删掉。

与之互补的是另一套**数据根(Data Root)**解析:用户个人数据(cv.mdconfig/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. 模式路由表:从 $modemodes/*.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-inboxeu-swe/eu-fintech 映射到 regional/ 子目录下的地区模式——用户输入的是短别名,模式文件在子目录中。
  • 子命令式命名interview/planinterview/practiceinterview/debrief 用斜杠表达"父模式 + 子动作",对应 modes/interview/ 子目录中的文档,而裸 interview 是交互式画像/简历引导。
  • 落点文件约定:模式名即 modes/{mode}.md 文件名,例如 ofertamodes/oferta.mdpdfmodes/pdf.mdscanmodes/scan.mdpipelinemodes/pipeline.mdtrackermodes/tracker.mdcovermodes/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. If language.modes_dir supplies 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.mdmodes/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-pipelineofertaofertaspdftextcontactoapplypipelinescanbatch

第二级:独立模式(带画像与自定义上下文)。加载顺序为 modes/_profile.md(若存在)+ modes/_custom.md(若存在)+ modes/{mode}.md,不加载 _shared.md。适用于:trackeragent-inboxdeepinterview-prepinterviewregional/eu-sweinterview/planinterview/practiceinterview/debrieflatexlatex-textrainingprojectpatternstitlesupskillfollowupreply-watchoutcomecoveremailaddoffer-prepdiscover

第三级:委派给子代理(worker/subagent)的模式scanapply(配合 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 执行目标模式文档。

从源码结构看,这种"文档即代码"的路由之所以可行,依赖三条仓库级支撑:

  1. 加载顺序在模式层被再次声明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.mdmodes/_custom.template.md)。
  2. 子代理委派带有成本护栏_shared.md 的 "Subagent delegation (cost guardrail)" 一节规定:任何为 career-ops 派生的子代理都是单次 worker——不得再派生子代理、不得调用其他 Skill(尤其开放式递归研究 Skill),公司与薪资研究必须内联完成。这是对"一个 /career-ops <JD> 绝不能膨胀成自复制 agent 群"的显式约束,与 SKILL.md 的 Agent(...) 调用形成"授权 + 限权"的配对设计。
  3. 加载的上下文决定评估行为。被第一级模式加载的 _shared.md 同时携带评分体系(五维 → 1-5 全局分)、Spend Tier 模型路由表(config/profile.ymlspend_tier 取值 economy/standard/premium,缺省 standard)以及 NEVER/ALWAYS 规则集。路由器虽然只负责"加载",但加载清单本身决定了每个模式能"看见"哪些系统规则——这也是把 auto-pipeline/oferta 等重评估模式归入第一级的原因。

8. 路由器文档与规范副本的同步机制:如何保证七份入口不漂移

最后一节回到工程实现:既然 .kimi/skills/career-ops/SKILL.md 等七条入口都是指向 .agents/skills/career-ops/SKILL.md 的符号链接或物化拷贝,如何防止"改了规范副本、旧拷贝没同步"?仓库给出了三层机制:

  1. 指针约定scaffolder/bin/skill-entrypoints.mjs):ensureSkillEntrypoints() 在初始化时补齐缺失入口(写入指针文本 ../../../.agents/skills/career-ops/SKILL.md);materializeSkillEntrypoints() 专门处理"文件系统不支持符号链接"的场景——当某入口是一个普通文件且内容恰等于指针字符串时,就地重写为规范内容。两个函数都以 CANONICAL_SKILL_PATH 为唯一内容源。
  2. 测试强约束tests/skill-project-root.test.mjs):如前所述,它遍历全部 8 条入口,验证根目录解析结果等于仓库根、路由规则文本存在;并针对 Git 索引模式 120000(符号链接在 Windows 等平台上可能以"内容即目标路径"的普通文件形式检出)构造 fixture,验证 readSkillEntrypoint() 能通过索引模式把"指针文件"还原为真实目标内容再解析根目录。测试注释标明该行为对应 issue #3332。
  3. 系统更新复用同一入口集合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 入口"的参考实现。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388