- name
- job-hunt
- description
- 求职猎手主入口。上传招聘平台岗位详情页截图(Boss直聘、智联招聘、前程无忧等均可),与你的简历做 STAR 匹配分析,生成定制简历和开场白,按匹配度排序产出 shortlist。支持子命令:fetch / analyze / tailor / status / clean。
# job-hunt 求职猎手
## ⚡ 全流程自动执行约束(最高优先级,全程有效)
**仅适用于 `/job-hunt` 无参数的全流程模式。**
用户提供简历 + 截图确认后,**剩余所有步骤全部自动连续执行,直到输出 shortlist**。
- **Step 3 fetcher 返回 → Step 7 之间:严禁输出任何文字**。输出文字 = turn 结束 = 等待用户 = 流程中断
- 每一步结束后,立即调用下一步所需的工具(Skill / Bash / Read / Write),不插入任何文字
- 子 skill(fetcher / analyzer / tailor)的返回结果是内部数据,**不得回显、不得复述、不得向用户展开汇报,直接用于下一步**
- **禁止**在步骤之间询问用户「是否继续」「要不要开始下一步」
违反上述约束 = 流程中断,用户体验完全崩溃。
---
## 子命令路由
根据用户输入判断执行路径:
| 用户输入 | 执行 |
|---|---|
| `/job-hunt` 无参数 | 全流程(Step 1→7) |
| `/job-hunt fetch` | 只执行截图导入(Step 1+2+3) |
| `/job-hunt analyze` | 只执行分析(Step 1+4) |
| `/job-hunt tailor` | 执行排序(Step 5)+ 定制(Step 6) |
| `/job-hunt status` | 输出当前 run 状态(Step 7b) |
| `/job-hunt clean` | 强制清理所有缓存和产物(Step 8) |
---
## Step 1:确定工作目录与初始化
`work_dir` = Claude 启动时所在的当前目录(`./` 的绝对路径)。
用 Bash 获取:`pwd`,将结果作为 `work_dir`。
`data_dir` = `<data_dir>/jobHuntSkillData`(所有数据文件统一放在这一层目录下,不直接写入 `work_dir`)。
确保以下目录存在(用 Bash `mkdir -p` 创建):
```
<data_dir>/.work/jd-pool/
<data_dir>/output/
```
生成 `run_id`(格式:`YYYY-MM-DD-HHMM`,使用当前本地时间)。
创建目录 `<data_dir>/output/<run_id>/`。
初始化 `<data_dir>/output/<run_id>/state.json`:
```json
{
"run_id": "<run_id>",
"phase": "init",
"stages": {
"fetched": [],
"analyzed": [],
"analysis_errors": [],
"tailored": []
},
"last_error": null,
"checkpoint_at": "<当前 ISO 8601 时间>"
}
```
**后续所有步骤中凡涉及路径的地方,一律使用 `data_dir` 代替 `work_dir`**。传给子 skill 的 `work_dir` 参数也传入 `data_dir` 的值。
**子命令特殊处理**:若当前子命令为 `analyze`、`tailor` 或 `status`,在生成新 run_id 之前,先扫描 `<data_dir>/output/` 下已有的 run 目录(格式 `YYYY-MM-DD-HHMM`),若存在则**复用最新一个 run_id**(不创建新目录,读取已有 state.json 继续使用);若不存在,则按上述流程创建新 run_id。
---
## Step 2:获取简历
(全流程、fetch、analyze 子命令时执行)
检查 `<data_dir>/.work/resume.md` 是否存在:
**若已存在**:告知用户:
「已检测到上次保存的简历,继续使用。
如需更换简历,直接把新简历发给我(支持文件 / 路径 / 粘贴文本),我会帮你替换并重新评估。
如果继续用当前简历,告诉我「继续」。」
停止执行,等待用户回复:
- 用户发来新简历 → 按下方「若不存在」的方式处理,覆盖保存 `resume.md`,**进入 Step 2.5 重新评估**
- 用户说「继续」或其他非简历内容 → 跳过本步骤,跳过 Step 2.5,直接进入 Step 3
**若不存在**:提示用户提供简历:
「请提供你的简历,支持以下两种方式:
① 发送 `.md` 格式简历文件,或告诉我文件的本地路径(如 `/Users/xxx/resume.md`)
② **直接将简历文字粘贴到消息框发送**(最快最稳,从任意 PDF/Word 阅读器全选复制即可)
不支持直接上传 PDF/Word 等版式文件——这类格式跨环境解析容易失真或失败。请用方式 ② 直接贴文本。」
根据用户提供方式处理:
- **发文件(.md)/ 文件路径(.md)**:用 Read 工具或 Bash 读取文件内容
- **发文件 / 文件路径(其他格式,如 .pdf / .docx / .doc / .txt / .rtf 等)**:**不要尝试任何形式的解析**,直接告知用户:
「暂不支持 PDF / Word 等版式文件上传(跨环境解析容易失真)。请改用:
① 用任意 PDF/Word 阅读器打开简历,全选(Cmd/Ctrl+A)→ 复制(Cmd/Ctrl+C)→ 粘贴到这里发给我
② 或将简历另存为 `.md` 格式后重发」
停止执行,等待用户重新操作
- **粘贴文本**:按下方「粘贴文本处理流程」执行
⛔ **伦理硬约束**:无论用户提供什么格式,**绝对禁止凭空编造、想象、推断简历内容**。识别不了的格式就停止流程让用户重传,绝不生成假简历。
**.md 文件分支处理**:读取内容后直接写入 `<data_dir>/.work/resume.md`,告知用户「✅ 简历已保存。」,进入 Step 2.5。
---
### 粘贴文本处理流程
仅当用户走「粘贴文本」分支时执行:
#### 步骤 1:保存原始留底
将用户粘贴的**原始内容**(一字不改)写入 `<data_dir>/.work/resume.raw.md`。这是任何后续清洗的回滚备份。
#### 步骤 2:启发式检测格式是否已 OK
判断粘贴文本是否「已是结构化 Markdown」,必须**同时**满足以下条件:
- 包含 ≥ 2 处章节标记(行首是 `#`、`##` 或 `1.` / `2.` 这类有序列表标号)
- 包含 ≥ 2 处列表标记(行首是 `-` 或 `*` 或 `+` 后接空格)
- 平均非空行长度 ≥ 15 个字符(说明没被一行一词强行断开)
- 不存在大量"单字孤立行"(连续 5 行以上每行只有 1-3 字符的情况)
**全部满足**:直接把原始内容写入 `<data_dir>/.work/resume.md`,告知用户「✅ 简历已保存(格式良好,直接使用)。」,进入 Step 2.5。**不询问、不清洗**。
**任一不满足**:进入步骤 3。
#### 步骤 3:询问用户选择
**关键约束:**
1. **必须输出用户粘贴的完整简历原文**(一字不少,不是预览、不是摘要、不是前 N 字截断)
2. 简历原文之后才是 A/B 问题
3. A/B 问题必须是整条消息的**最后一段**,让用户视线最后落点就是 `👉 回复 A 或 B`
4. 简历原文之前**不要**写任何说明文字(如"以下是你的简历""收到了"等开场白)
5. 简历原文用代码块(``` 包裹)显示,避免 Markdown 渲染干扰原始格式
输出格式严格如下:
````
```
<用户粘贴的简历完整原文,一字不动>
```
---
📝 上面是你粘贴的简历内容。看起来格式有些问题——可能存在断行、缺章节标记等,常见于从 PDF/Word 复制带过来的。
两种处理方式:
A. **帮我整理一下** —— 只动格式:合并被断开的同段、还原项目符号、加章节标题;**不动一个字**
B. **保留我的原始格式** —— 我就要这样,不要改
👉 回复 A 或 B
````
停止执行,等待用户回复。
**禁止**:
- 截断简历内容(即使简历很长也要全部贴出)
- 在 A/B 问题之后再追加任何文字
- 在简历原文上方加任何标题/说明(直接代码块开始)
#### 步骤 4:按用户选择处理
- **用户回 A**(或语义等价:「整理」「清洗」「帮我弄」「随便」等)→ 进入步骤 5 做结构归一化
- **用户回 B**(或语义等价:「不动」「原样」「保留」「不要改」等)→ 把原始内容写入 `<data_dir>/.work/resume.md`,告知用户「✅ 已按你的原始格式保存。」,进入 Step 2.5
- **用户回模糊内容**(如「?」「啥意思」)→ 简短重新解释一次 A/B 含义,再等回复
- **用户直接发了新简历**→ 当作重新粘贴处理,回到步骤 1
#### 步骤 5:执行结构归一化(仅 A 路径)
**严格只动格式,不动一个字**。允许做和绝对禁止做的清单:
✅ **允许做**:
| 操作 | 说明 |
|---|---|
| 合并被强行断开的同句行 | `负责抖音\n小视频\n前端开发` → `负责抖音小视频前端开发`。判断标准:上一行不以句末标点结尾(`。!?.!?`)且下一行不像新章节/列表项的开始 |
| 合并被拆散的标题/姓名 | `张\n三` → `张三`;`工作经\n历` → `工作经历` |
| 移除连续空行 | 3 个及以上连续空行 → 保留 1 个 |
| 还原项目符号 | 段首孤立的 `·` `●` `▪` `◦` `▶` → 替换为 `- ` |
| 还原章节标题层级 | 识别"个人信息""工作经历""项目经历""项目经验""教育背景""专业技能""自我评价"等常见章节关键词,在前面加 `## ` |
| 规范缩进 | tab 字符 → 2 空格;行首过多空格统一为 2 空格倍数 |
| 修复编码异常 | 删除零宽字符 `` `` 等不可见字符 |
| 删除明显多栏混排导致的乱序片段 | 仅当能 100% 确定是多栏问题且能正确还原时;不确定就不要动 |
❌ **绝对禁止**:
- 增加、删除、改写任何文字内容(哪怕一个字)
- 修正错别字(用户写「杭洲」就保留「杭洲」)
- 翻译、总结、精简、扩写
- 根据上下文推断补齐缺失信息
- 添加用户没提的章节、技能、经历
- 改变事件顺序、时间、数字
**违反任何一条 ❌ 即触发伦理硬约束告警,必须停止并告知用户。**
清洗完成后:
- 把清洗结果写入 `<data_dir>/.work/resume.md`
- 简短告知用户清洗量:「📝 已整理简历格式:合并 N 处断行 / 还原 M 个项目符号 / 加 K 个章节标题。原始版本已留底(如不满意可说"还原原版")。」
- 进入 Step 2.5
---
### 用户事后反悔(任何阶段)
当用户在后续流程中说出「**还原原版**」「**用原版**」「**用我贴的那版**」「**不要清洗后的**」等语义等价指令时:
1. 检查 `<data_dir>/.work/resume.raw.md` 是否存在
- 不存在 → 告知「未找到原始留底版本(可能本次简历没经过清洗)。如需更换,直接重新粘贴或发送新简历。」
- 存在 → 用 Bash 把 `resume.raw.md` 复制覆盖到 `resume.md`
2. 告知用户「✅ 已切回原始版本。」
3. 重新进入 Step 2.5 评估
---
## Step 2.5:简历质量评估
(仅在用户本次会话中新提供了简历时执行;复用缓存的直接跳到 Step 3)
读取 `<data_dir>/.work/resume.md`,找出所有**可评估单元**,逐一打分,输出结果。
⛔ **必须先过滤,再评估。以下三条规则是强制先决条件,不允许跳过或绕过:**
- 在任何评估行为之前,必须完成规则一→二→三的完整筛选流程
- **不得根据公司名、职位名、部门名推断该段经历的工作内容进行评估**——评估对象只能是简历中白纸黑字写出的行动动词和结果描述
- 只有通过规则三的文字内容,才能出现在「逐段分析」中;未通过筛选的区块/行,一律不出现
**三条判断规则,按顺序执行:**
---
**规则一:整个区块跳过(不看内容,直接略过)**
下列区块不论写了什么,整体跳过,不评估:
- 个人信息 / 基本信息 / 联系方式
- 专业技能 / 技能特长 / 技术能力 / 工具使用 / 技术栈
- 教育背景 / 教育经历 / 学历
- 自我评价 / 个人简介 / 求职意向
- 证书 / 奖项 / 荣誉
**区块名不限于以上文字**,只要语义属于上述类型(如「我的技能」「掌握的工具」「所获奖励」),同样整体跳过。
⚠️ **合并区块的处理**:如果区块名同时包含工作和教育(如「工作及教育经历」「教育与工作背景」),**不整体跳过**,进入规则二逐行判断。其中属于教育条目的行(含学校名、专业名、学位、在校时间)按规则二同样会被跳过。
**其他所有区块**(工作经历、项目经历、实习经历、创业经历、兼职经历,或任何自定义名称的经历类区块)进入规则二。
---
**规则二:区块内的标题行跳过**
经历类区块内,每一行单独判断。如果这一行**同时满足以下两点**,跳过:
- 没有行动动词(负责、主导、设计、推动、搭建、优化、完成、实现、带领等)
- 没有结果描述(提升、降低、增长、减少、达到、超过、节省,或具体数字/百分比)
⚠️ **重要:职位名、部门名中含有的动词性词语不算行动动词。** 例如"运营组""品牌部""设计科""管理岗"——这些是组织单元的名称标签,不是对个人行为的描述,不触发评估。判断依据是:这个词是在描述「这个人做了什么」,还是在说明「这个部门/岗位叫什么名字」。
例如:
- 「字节跳动 · 产品经理 · 2021.03—2023.06」→ ❌ 跳过(无动词无结果)
- 「某文化传媒公司 2023.7~2025.11 新媒体-运营组」→ ❌ 跳过("运营"是部门名,不是行动描述)
- 「某互联网科技公司 2022.6~2023.6 市场部-品牌组」→ ❌ 跳过("品牌"是部门名,不是行动描述)
- 「xx大学 2018.9~2022.6 新闻传播专业-本科」→ ❌ 跳过(教育条目,无行动无结果)
- 「项目名称:xxx 平台 | 角色:负责人 | 2022.01—2022.06」→ ❌ 跳过(无动词无结果)
---
**⚠️ 空区块规则(规则二执行完后立即检查)**
若某经历类区块经规则二处理后,**全部行均被跳过**(即该区块内无任何行含有行动动词或结果描述),则该区块**不进入下方三维度打分**,**不得为该区块生成任何评估条目,也不得从其他区块借调内容来填充**。
典型场景:「工作及教育经历」区块内只有公司名+时间+部门名三行头部信息,规则二全部跳过后,该区块不参与逐段打分。
**📛 但空壳的工作/实习经历不能静默消失,必须显著警示:**
在跳过这类区块时,记录其中属于**工作/实习经历**的空壳条目(公司名 + 时间段,例如「某文化传媒公司 2023.7~2025.11」)。只要存在至少一条,就必须在评估输出**最顶部**输出「空壳经历警示」块(见下方输出格式)。
原因:空壳工作经历是简历最致命的短板,且 tailor 阶段**无法**为它做任何改写(伦理红线禁止编造内容)。若这些空壳覆盖了用户的主要工作年限,补全它们比任何简历定制都重要——必须让用户清楚看到,而不是让它默默消失。教育条目空壳不在此警示范围(教育经历本就无需行动/结果)。
---
**规则三:剩下的内容才评估**
通过前两条规则筛选后剩下的句子/条目,即为**可评估单元**,进入下方三维度打分。
⚠️ 标题行与描述混写在同一行时,按规则二判断:只要含有行动动词或结果描述,就评估。
- 「在字节跳动担任产品经理期间主导了 xx 项目落地,DAU 提升 40%」→ ✅ 评估(含动词+结果)
### 评估维度
⛔ **只有通过上方规则三筛选的内容才能进入此步骤。** 若某区块(如「工作及教育经历」)内所有行均被规则二跳过,该区块整体不生成任何评估条目——即使你能判断那是一段工作经历,也不评估,因为简历里没有写任何行动或结果。
每段描述内容按以下三个维度打分(✅ 合格 / ⚠️ 薄弱 / ❌ 缺失):
| 维度 | ✅ 合格 | ⚠️ 薄弱 | ❌ 缺失 |
|---|---|---|---|
| **场景/问题**(S/P) | 有明确业务背景或要解决的问题 | 背景模糊,一笔带过 | 无任何场景描述 |
| **行动**(A) | 具体描述「我做了什么」,有方法/手段 | 只写职责,没有行动细节 | 缺失 |
| **结果**(R) | 有量化数字,或明确的业务价值 | 结果模糊(如"效果不错") | 无结果描述 |
额外检查:
- 是否大量使用「负责」「参与」「协助」等被动词(减分项,建议改为主动动词)
- 是否有明显可量化但留白的指标(tailor 阶段会插入 `[请填写:xxx]` 占位)
### 输出格式
```
📋 简历质量评估
<【仅当检测到空壳工作/实习经历时,在最顶部输出此块;否则整块省略】
⚠️ 重要提醒:你有 <N> 段工作经历是空的
· <公司名> · <时间段>
· <公司名> · <时间段>
这几段只有公司名和时间,没写任何工作内容和成果。这是你简历最大的短板:
- 系统算匹配度时,这些经历因为没内容会被整体跳过,匹配分会明显偏低
- 定制简历时,AI 不会替你编造内容(伦理红线),这几段只能原样保留、无法优化
👉 补全这几段的「做了什么 + 拿到什么结果」,比任何简历定制都重要。强烈建议先选 A 补这部分。
>
整体:<一句话总结,如"行动描述较充分,但结果量化普遍缺失,建议优先补充">
逐段分析:
【公司名 · 职位名】
场景/问题 ✅ 行动 ⚠️ 结果 ❌
问题:<具体说明薄弱点>
建议:<一句话改写方向>
【项目名 · 角色】
场景/问题 ⚠️ 行动 ✅ 结果 ⚠️
问题:<说明>
建议:<建议>
(…其他段落)
⚡ 最值得优先改的 1-2 件事:
1. <最高优先级>
2. <次优先级>
🔧 专业技能板块诊断:
<独立于上方 STAR 评估,专项检查技能板块(语义识别板块,任意名称均适用;若无标题,按内容特征识别:连续排列的技能词/工具名/框架名)。按以下四类问题逐一检查,有问题输出具体条目,无问题省略该类;若整个技能板块无任何问题,省略「🔧 专业技能板块诊断」整节:>
- **虚浮词/空泛声称**:「精通」「熟练掌握」后面没有具体场景或工具,或「精通所有前端技术」这类大而无当的表述。
针对每条有问题的条目给出**改写示范**,例:
「精通 Python」→「熟练使用 Python(Flask/pandas,用于 xx 数据处理)」
- **缺工具名/过于宽泛**:只写了能力方向,没有写具体工具/平台/框架。例「熟悉数据分析」「了解前端开发」。
针对每条给出建议补全方向,例:
「熟悉数据分析」→「熟悉数据分析(SQL / Tableau / Power BI)」
- **无程度分层**:全部条目都是同一程度词(全是「熟悉」或全是「精通」),无法体现能力深浅。
如有此问题,提示按「熟练 / 熟悉 / 了解」或用户实际情况重新分级。
- **与目标岗位明显偏差**:<此项留空,tailor 阶段结合具体 JD 再给建议;Step 2.5 无 JD 上下文,不评估此项>
<仅当上述四类至少有一类有实质问题时,才输出「🔧 专业技能板块诊断」节;若技能板块写得合理(程度词有分层、有工具名、无大量空泛声称),整节省略,不输出任何占位文字。>
---
如何继续?
A. 我自己去改简历,改完发给你重新评估
B. 先不改,用当前简历继续
```
### 用户选择处理
**选 A(修改简历)**:
- 告知用户:「好的,修改完后直接把新简历发给我(支持文件 / 路径 / 粘贴文本)。」
- 停止执行,等待用户重新发送简历
- 收到后按 Step 2 同样方式处理,覆盖保存 `resume.md`
- **重新执行 Step 2.5**(循环,直到用户选 B)
**选 B(继续)**:
- 告知用户:「好的,继续。」
- 执行 Step 3
---
## Step 3:岗位收集与导入
(全流程、fetch 子命令时执行)
提示用户:
「请上传你感兴趣的岗位,支持以下方式:
【方式一】直接发岗位详情页截图(任意招聘平台,Boss/智联/猎聘/拉勾等)
一次可发多张,📌 一张截图 = 一个岗位
岗位信息长请用长截图,截图里至少要有岗位名(公司名可选)
【方式二】告诉我截图所在的目录路径(如 /Users/xxx/Desktop/jobs)
我会自动读取该目录下的所有图片,以及 .jobs.json 文件(不递归子目录)
【方式三】用浏览器插件导出 .jobs.json 文件后一键拖进来(支持 Boss直聘 / 前程无忧 / 智联招聘 / 猎聘)
👉 没装插件?查看 README 安装说明」
发出上方提示后,停止执行,等待用户发送截图或消息。不得提前进入循环。
**判断用户输入类型:**
| 输入 | 处理分支 |
|---|---|
| 图片附件(一张或多张) | 分支 A:截图解析 |
| 文件路径,以 `.jobs.json` 结尾 | 分支 C:JSON 导入 |
| 目录路径 | 分支 B:扫描目录(图片 + .jobs.json 混合) |
### 分支 A:截图解析
调用 Skill 工具,加载 `job-hunt-fetcher` skill,传入:
- `work_dir`:<data_dir 的值>
- `run_id`:<当前 run_id>
- `screenshots`:<本批次截图>
fetcher 内部处理(含分组确认交互),完成后写入 jd-pool。
### 分支 B:扫描目录
用 Bash 验证目录存在:
```
ls "<目录路径>"
```
若不存在,告知用户「目录不存在,请确认路径后重新发送。」并重新等待。
用 Bash 列出该目录下的图片文件和 .jobs.json 文件(只读当前目录,不递归子目录):
```
find "<目录>" -maxdepth 1 -type f \( -iname "*.png" -o -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.webp" -o -iname "*.jobs.json" \) | sort
```
若结果为空,告知用户「该目录下没有找到图片或 .jobs.json 文件,请确认后重新发送。」并重新等待。
告知用户找到了哪些文件:「找到 <N> 张截图 + <M> 个 .jobs.json 文件,开始处理。」
按文件类型分别处理:
- 图片 → 分支 A(调 fetcher)
- 每个 .jobs.json → 分支 C(调 import_jobs.py)
### 分支 C:JSON 导入
用 Bash 调用 import_jobs.py:
```bash
if command -v python3 >/dev/null 2>&1; then PYBIN=python3; \
elif command -v python >/dev/null 2>&1; then PYBIN=python; \
else echo "PYTHON_MISSING"; exit 1; fi
"$PYBIN" "$HOME/.claude/skills/job-hunt/import_jobs.py" "<data_dir>" "<json_path>"
```
预期 stdout:
- `OK: 已导入 N 个岗位 -> ...` → 成功,继续
- `ERROR: ...` → 告知用户「.jobs.json 处理失败:<原因>。请检查文件后重发。」并重新等待
- `PYTHON_MISSING` → 告知用户「需要 Python 3 才能处理 .jobs.json,请安装后重试。」并停止
### 公共后续
不论分支 A/B/C,全部处理完成后:
用 Bash 工具扫描 `<data_dir>/.work/jd-pool/` 下所有 `.md` 文件(排除 `.analysis.md`),读取 frontmatter 中 `run_id` 等于当前 run_id 的文件(extension-import 也包含),提取其 `id` 字段,作为本批次 ID 列表;将 ID 列表追加到 `state.json` 的 `stages.fetched`,更新 `checkpoint_at` 和 `phase` 为 `"fetched"`。
若子命令为 `fetch`:告知用户「✅ JD 导入完成,共 <N> 个岗位。运行 /job-hunt analyze 开始分析。」并停止。
**【全流程】⚠️ 本步骤完成后到 Step 7 之间,严禁输出任何文字(包括不得回显或复述子 skill 的输出内容)。state.json 更新完毕后,下一个动作必须是 Skill 工具调用(执行 Step 4),不得有任何文字夹在中间。**
---
## Step 4:分析(job-hunt-analyzer)
(全流程、analyze 子命令时执行)
确定待分析 JD 列表:
- 扫描 `<data_dir>/.work/jd-pool/` 下所有 `.md` 文件(排除 `.analysis.md` 结尾的文件)
- 读取每个文件的 frontmatter,筛选 `status.analyzed: false` 的文件,提取其 `id` 字段
- 排除 `state.json.stages.analysis_errors` 中已记录失败的 ID
若列表为空,告知用户「jd-pool 中没有待分析的 JD,请先上传截图。」并停止。
调用 Skill 工具,加载 `job-hunt-analyzer` skill,传入:
View on GitHub