career-ops 开源贡献实战指南:从首个 good-first-issue 到维护者的协作全流程
【免费下载链接】career-opsOpen-source AI job search: scan job portals, evaluate listings into a structured A-H report with a global 1-5 score, tailor your CV, track applications — runs locally in your AI coding CLI (Claude Code, Codex, OpenCode, Antigravity…)项目地址: https://gitcode.com/GitHub_Trending/ca/career-ops
career-ops 是一个本地优先、人机协作的 AI 求职开源工具:它扫描招聘网站、把职位评估为结构化的 A–H 报告并给出 1–5 分评分、为你量身定制简历并跟踪申请,全部在你的 AI 编码 CLI(Claude Code、Codex、OpenCode 等)中本地运行。本文以仓库根目录的 CONTRIBUTING.md 为骨架,系统拆解该项目的贡献协作流程——包括 PR 提交流程、核心与插件层(Plugin)的架构边界、"Source Indexing Policy(数据源索引政策)"的五条硬性规则、测试目录规范与本地开发命令,并结合源码与你手把手上手第一个合并的 PR。
一、为什么在 career-ops 做第一次开源贡献
项目文档给出了几个非常实在的理由,值得先建立共识:
- 你已经天然理解问题域。career-ops 是一个求职工具——如果你正在找工作,你比多数人更懂它的痛点,也天然是更好的贡献者。
- 你的代码会被真实的人使用。贡献被合并后,你的名字进入的是一个真实项目的历史,而不是玩具仓库(仓库文档记载该仓库已在 GitHub Trending 长期上榜、Star 数超过 55K)。
- 响应快。文档承诺"开 issue 或 PR 通常一两天内就有回音,没有黑洞"。
- 有极小的上坡路径(tiny on-ramps)。
good first issue都被切成小块:带时间预估、可复制的模式、明确的"完成"定义,第一次 PR 是稳赢而不是迷宫。 - 有真实的 Code Review。每个 PR 都会被人工阅读,不淹没在 bot 噪声里,也不合并"AI 垃圾"。
- 有明确的晋升路径。持续高质量贡献者会得到公开署名,并被邀请承担更大角色(reviewer → maintainer)。
注意这些描述均来自 CONTRIBUTING.md 原文与仓库自身文档,Star 数与增长数据仅作为仓库文档陈述引用,不代表对当前时点的实时核验。
二、提交 PR 之前:先对方向,再写代码
Feature 类变更先开 issue
对于新功能、新的 mode(模式)或命令、架构级变更,文档要求先开 issue 讨论。原因是避免你在一个"最终会被我们转向"的方向上投入时间,让大家在写代码之前先对齐方向。
无需 issue、可直接 PR 的类别
以下类别直通 PR 完全欢迎、不需要先开 issue,因为流程不应拖慢它们,且这正是项目最想要的贡献:
- Bug 修复;
- 新的 zero-auth 扫描器 provider(无需鉴权即可读取公开职位数据的接入模块);
- 文档;
- 翻译。
文档同时提醒:一个绕过此步的大型 feature PR,如果不符合架构或路线图,可能被要求"先回到 issue"——这是范围对话(scope conversation),而不是对你工作质量的否定。
好 PR 的标准
- 修复了 Issues 里列出的 bug;
- 解决了经过讨论并获批的功能请求;
- 包含"改了什么、为什么改"的清晰描述;
- 遵循现有代码风格与项目哲学:简单、最小、质量优先于数量(simple, minimal, quality over quantity)。
Quick Start 七步走
- 开 issue 讨论你的想法;
- Fork 仓库;
- 建分支:
git checkout -b feature/my-feature; - 做改动;
- 用全新 clone 测试(参见 docs/SETUP.md);
- Commit 并 push;
- 开 Pull Request 并引用该 issue。
第 5 步强调"fresh clone"很有深意:career-ops 是本地运行工具,真实环境依赖(Node >= 18、Chromium/Playwright、dashboard 的 Go 工具链,见 package.json)只有在干净环境里跑通才可靠。
三、贡献什么:好点子分级与认领机制
入门级贡献
| 贡献方向 | 落点 |
|---|---|
| 为职位门户表补充公司 | templates/portals.example.yml |
| 把 modes 翻译成其他语言 | modes/下各语言目录(zh/、es/、de/、fr/等) |
| 改进文档 | docs/、README.*.md |
| 为不同岗位添加示例简历 | examples/ |
| 报 bug | 仓库 Issues |
更大规模的贡献
- 新的评估维度或评分逻辑(对应 A–H 报告与 1–5 评分体系);
- Dashboard TUI 功能(代码在 dashboard/,是一个独立的 Go 模块,含
go.mod/go.sum/main.go与平台相关的open_*.go); - 新的技能模式(skill modes)(在 modes/);
- 脚本改进(各种
.mjs工具,根目录下即可见大量入口)。
/assign认领机制如何保持公平
对任意good first issue评论/assign即可认领,无需等待维护者。公平性由三条规则保证:
- 认领会自动释放:7 天无动静后(第 3 天会收到友好提醒)该 issue 回到可认领窗口,不会卡死。
/extend无理由重启计时,/unassign干净放手;只要挂着打开的 PR 就暂停计时。 - 为新贡献者预留:good-first-issue 面向在此仓库合并少于 3 个 PR 的人(
first-timers-only标签则严格要求是"第一个"),一次只能认领一个,确保首次贡献者永远有路可走。超过该阶段后,help wanted标签是你的主战场。 - 认领不是贡献的前提:直接对任何未分配 issue 提 PR 永远欢迎。
四、贡献者阶梯:公开、可预期的晋升路径
项目有明确梯队,且"公开署名、主动邀请":
- 首次贡献者(First-time contributor)——你合并了一个 PR,欢迎加入;
- 可信贡献者(Trusted contributor)——几个扎实的合并后,你的 PR 会走快速通道,并在相关工作里 @ 你;
- 审阅者(Reviewer)——帮助分流与审阅他人 PR(由项目方邀请);
- 维护者(Maintainer)——参与掌舵项目方向。
想多做一点?直接在 issue 里说出来即可。
五、接管被放弃的 PR:public、可预测的三级阶梯
"生活会发生:一个 PR 收到 review,作者转行了,有用的工作停在 80% 完成。"career-ops 的做法不是让 bot 埋掉它,也不是任它腐烂,而是走一条公开且可预测的阶梯:
- Review 一轮后静默两周,维护者将该 PR 选入接管流程(
adoption/track),并友好提醒原作者:"还是你的,不急。" - 再过两周,进行第二次确认,并写明后续计划:若再静默两周,工作将开放认领。
- 只有到这一步,维护者(绝不通过 bot)才会感谢并关闭原 PR,同时开一个标记为
adoptable的伴生 issue,指向原分支并逐条列出剩余工作。
接管一个被放弃的 PR 是最有价值的首次贡献之一:diff 基本完成、review 已经写好、剩余工作范围明确。做法是开一个新 PR并携带原始 commits(git 会保留作者署名),或用Co-authored-by:尾部追加原文作者。两位贡献者都得到署名:原作者署名工作,你署名落地。
如果你是回来的原作者:只要还没被别人完成,工作随时可收回——在 issue 上说一声即可;阶梯任何一步上你的一次评论或 push 都会完整重置计时。
六、"别人的开放 PR 仍然是别人的":边界与自动化规则
上述接管阶梯只针对已被放弃的工作。一个作者仍在活跃的开放 PR 是另一回事,这条线非常明确:
- 不要开一个"重新解决别人冲突"的 PR。在评论区指出某 PR 已冲突确实有用;但把它 rebase 到自己的分支再开替代 PR 不行——因为替代 PR 合并会关闭原 PR,原作者得到的将是
closed而不是本该属于他的merged。那个徽章是贡献者从这个项目带走的大部分东西,不归我们重新分配。 - 如果冲突源自项目方自己的合并,修复责任在项目方:维护者会在作者自己的分支上解决冲突(这正是 "Allow edits by maintainers" 的意义)、跑全套测试、保留原 PR 与作者身份原封不动。冲突来自其他任何地方,则由作者在自己方便时 rebase——"没人被催着赶这个时间表"。
- 自动化同样受约束:bot 在别人分支上开替代 PR 等于以更高音量做同一件事;在别人线程里贴自动化分流意见("别两个都合并""把 #X 当作主 PR")会读起来像项目决策。合并决定只能由维护者做出。自动化 Agent 只能在自己 operator 开的 PR 上评论;在他人 PR 上的自动化评论会被当作离题内容最小化。你以自己名义手写的 Review,在任何 PR 上都欢迎。
- 超出"解决冲突"的改进欢迎,但不要钉在别人 PR 上:在讨论串里提出、让作者决定,或等它合并后开自己的 PR。
七、架构边界:core 与共享层(plugin-first)怎么分
career-ops core 的定位是local-first(本地优先)与 human-in-the-loop(人在回路):它跑在你的机器上,起草的申请材料由你审查并提交。而集中式基础设施——托管职位聚合、共享匹配服务、代理或 Workers——不属于 core,那是更重的、未来作为独立、opt-in 服务的方向。
动手之前的经验法则:provider 模块、语言、CLI 支持、core 主路径上的 modes、dashboard、文档与修复 → 属于 core。更大的集中化/自动化想法(托管层、auto-apply、爬取基础设施)→ 先在方向讨论里发起,而不是交一个无法合并的大型 PR。
平行功能四问(the parallel-feature test)
career-ops 对很多事说"是":provider、语言、CLI 支持、修复。它刻意挑剔的是平行功能——那些与求职主路径相邻、单独看也各自有用的东西。因为每个合并的 feature 都是"永远维护它"的承诺(文档、测试、agent 上下文、升级路径),所以"它写得好不好"根本不是门槛。提这类功能前,先自问四个项目自己也在用的问题:
- 它在核心主路径上吗?核心路径是:发现职位(discover)→ 评估(evaluate)→ 定制(tailor)→ 申请(apply)→ 跟踪(track)→ 闭环(close the loop)。服务于这条路径的基础设施(去重、原子写入、状态转换台账)即使不可见也属于核心。而在路径旁边的功能(联系人管理、日历、笔记)从 plugin 起步。
- 谁来付维护成本?一个把某个工作流做到极致、却给所有人增加表面积(新数据文件、新脚本、新模式)的 feature,需要被证明有真实需求(多个人的 issue,而不是一个人),否则就应该住进 plugin。
- Plugin-first,凭证据毕业。相邻功能先做成 plugin(契约见 docs/PLUGINS.md):你自己掌控发布节奏,项目把它登记在 registry 里。若 plugin 获得真实采用,项目会考虑把它"毕业"进 core——基于证据的晋升而非关卡,这是 WordPress 运营 feature-projects 的方式。
- 它符合项目的形状吗?一个破坏既有模式的 mode 或 API,会给每个未来的用户和贡献者制造认知负担,即使它工作正常。合并前请先接受"与代码库一致的拼写/写法"这一要求。
四条任一不通过,是"路由问题"而非"拒绝":先开 issue,我们会告诉你去哪扇门——core、plugin 还是独立项目。plugin registry 提供真实的分发渠道,而今天不适合 core 的想法,仍可能成为你交付的最有用的东西。
八、Source Indexing Policy:所有数据源共用的一把尺
career-ops 从公开来源读取职位:ATS、招聘板、公司招聘页、人才网络。这套政策是每个来源都必须通过的唯一标准——无论谁提议,包括提交自家招聘板的运营者。项目"不评判来源的商业模式,只评判它的数据";这五条规则是 MANIFESTO.md(CareerOps 宣言)在数据源上的落地。
- 索引什么:任何"职位真实、可归属到可识别的雇主、且候选者可免费阅读与申请"的来源。宣言权利第 4 条"You never pay"同样适用于来源:对职位或申请设付费墙的来源,无论其他方面如何,都不会被索引。
- 规范 URL(Canonical URL):每条职位携带来源所暴露的、通往雇主的最短可验证路径(可用时用 ATS 或直投链接)。来源自身页面只能作为次级归属。
- 付费置顶到不了候选人:推广内容不能购买在 career-ops 中的位置——排序发生在每个用户自己的机器上,provider 遍历其来源的完整库存,维护者会对来源做响应偏差审计(API 总量 vs 站点总量、页面分布)。career-ops 自身不带任何赞助位。这是宣言权利第 8 条"Your agent works for you. Not for a platform, not for an employer."在数据层的强制执行。
- 索引不等于背书,分发也不是欠谁的:进入 registry 会把职位摆到安装用户群面前,真实、可衡量、且依赖渠道——但没有任何来源被欠着位置、流量或永久性。来源必须声明其运营者,且单一来源不得超过 registry 的 40%。
- 聚合层属于项目:一个 provider 只读它自己的来源。跨来源的聚合、排序、匹配与 registry 都住在 core 中,绝不委托给某个来源。
政策如何被逐条执行
可以读 docs/SOURCE_INDEXING_LOG.md:每个已收录来源一条记录,写清楚"检查了什么、怎么验证的"。文档特别说明该日志不是排名也不是承诺,"Verified"意味着"有人跑了命令并报告输出",而不是"声明被接受";涉及活端点检查会注明采样时间,因为线上检查会过期。
该文档里两个典型判例值得参考:
- remotli.ch(第一位在成文政策下过审的运营者自荐来源):验证了规则 1(无候选者付费墙)、规则 2(采样 121 行,雇主 URL 占比 100%)、规则 3(运营者自曝不加
remote=all只覆盖 392/921 条,合并的 provider 遍历全部 19 页)。**"披露在前,规则裁决,而不是靠对话"**正是政策存在的意义。 - a16z speedrun 人才网络(促成政策成文的案例):listing 后由贡献者读线上 feed 发现并修复了两个覆盖缺陷(分页容量 50 vs 100、单次上游瞬时故障导致整板中断)——这正是规则 3 针对的失效模式:"看着完整、实则部分覆盖"。
怎么提出一个新来源
想提案一个来源(自己的或别人的):
- 走source proposalissue 模板,逐条对照上面五条规则;或
- 直接提交带 provider 的 PR 也欢迎,合并前同样走这五条规则。
- 运营者声明在收录前须带外核实(out-of-band verification):一个可在来源自身域名下联系到的联系人,或等效的域名控制证明。运营者提议自己的招聘板完全没问题——"规则化门槛"正是为此而设。
从实现侧印证:一个 provider 是一个 providers/ 下的{name}.mjs模块,通过 providers/_registry.mjs 被 scan.mjs 与 verify-portals.mjs 自动加载——放入文件即完成注册,无手工登记。完整的接入契约、强制护栏与tests/providers/{name}.test.mjs必须覆盖的内容见 providers/ADDING_A_PROVIDER.md。
九、开发守则(Guidelines)
- 尽量让 modes保持语言无关(Claude 能同时处理 EN 与 ES);
- 脚本应优雅处理缺失文件——先
existsSync再readFileSync(这也是 tests 中大量用例覆盖的防御模式); - Dashboard 改动必须构建:
npm run build:dashboard,并用真实数据测试后再提交; - 不要提交个人数据:
cv.md、profile.yml、applications.md、reports/属于本地用户层,不进版本库。
十、明确不接受的 PR
以下清单是项目红线,任何类别都会被主动拒绝,了解它能在动手前省下大量时间:
- 爬取禁止自动化访问平台的 PR(如 LinkedIn 等)——为尊重第三方 ToS;
- 绕过人工审查、自动提交申请的 PR——career-ops 是决策支持工具,不是 spam bot(呼应宣言:Nothing is ever auto-submitted);
- 未经 issue 讨论就引入外部 API 依赖的 PR;
- 针对内置 plugin 的 feature PR(plugins/apify、plugins/gmail、plugins/notion)——内置 plugin 是稳定的reference seeds,要扩展就发布你自己的
career-ops-plugin-<id>,项目会登记它为"安装后即优先"的被维护后继;内置 plugin 只接收安全/兼容性修复; - 向 core 添加集中化或托管基础设施(代理、聚合服务、共享 Workers)——那属于独立 opt-in 服务而非 open-core;
- 以通用聚合索引作为依赖——把"整合众多来源的统一聚合层"作为第三方依赖接入;单读各招聘板的 provider 永远欢迎,统一的聚合层本身必须是一方(first-party);
- 把数据发给第三方服务的集成——需要第三方账号、或把简历/流水线/笔记推到外部服务的 provider 或同步功能。career-ops 本地优先、零密钥:求职数据留在你的机器上。本地读取公开职位 API 完全欢迎(内置 provider 就是这么工作的),把个人数据路由给别人的服务则不行;
- 主要消费者是第三方产品的集成——调用方是别人家产品(bot、SaaS、外部编排器)的模块/契约/适配器,即便代码本身通用,也属于 plugin 或独立项目,绝不属于 core;项目自身一方表面(官方 web 体验、可选的共享服务)例外;
- 向 README 添加第三方托管的入口或服务徽章——README 只保留项目控制的资产;基于 career-ops 构建的项目欢迎在社区里分享,但不能上首页;
- 包含个人数据的 PR(真实简历、邮箱、电话)——请用 examples/ 下的虚构数据。
十一、本地开发与测试规范(实测可复制)
仓库文档给出的一套核心开发命令,与 package.json 的 scripts 一一对应:
# 脚本/自检 npm run doctor # 设置与本地环境校验(对应根目录 doctor.mjs) node verify-pipeline.mjs # 流水线健康检查 node cv-sync-check.mjs # 配置一致性检查(简历相关配置同步) # Dashboard(Go TUI) npm run build:dashboard # 平台正确的 go build(调用根目录 build-dashboard.mjs) npm run serve:dashboard # 以仓库根目录为 --path 启动 TUI # 测试 node test-all.mjs # 全套测试 —— push / 开 PR 前必跑 node test-all.mjs --quick # 全套但跳过 dashboard 构建 node test-all.mjs --only providers/themuse # 只跑某个 provider 的测试新测试必须独立成文件:tests/ 的自动发现机制
任何新测试都应放在tests/下自己的文件里,而不是作为test-all.mjs里的编号小节。凡是匹配tests/**/*.test.mjs的文件都会被自动发现——无需注册、无需选编号。
从源码看这不是洁癖,而是真实的协作教训:见 test-all.mjs 头注——2026 年 8 月六个贡献者同时往test-all.mjs末尾加编号小节,六个人不约而同都选了60a,每个合并都迫使其余五个人 rebase——"六行测试代码换来约十五次 rebase 和六次串行化的 CI 运行"。而一个新文件不与任何人冲突,这些 PR 可以全部并行落地。实现上 test-all.mjs 的discoverTests()用readdirSync+ 字典序确定性遍历,还会跳过嵌套 checkout(worktree)防止误执行。
新增扫描 provider?读这份完整契约
见 providers/ADDING_A_PROVIDER.md:完整契约、强制护栏、以及tests/providers/{name}.test.mjs必须覆盖什么(例如禁止process.exit()、必须用测试助手计数、健康探测时的行为等,实际仓库的tests/providers/下已沉淀 100+ 个对应测试文件)。
Web 端测试布局
Web 套件位于 web/tests/,镜像被测模块在web/src/下的路径(如src/lib/clean-chips.mjs→tests/lib/clean-chips.test.mjs),命名为{module}.test.mjs。要点:
web/自己的npm test通过 glob 发现它们,同样无需注册;- 但不要让它们落在
web/src/里——Next.js 会扫描该目录树; - 写成
.mjs——node --test没有 TypeScript loader; - 布局细节见 web/README.md,并由根套件的 tests/web-test-layout.test.mjs 在每个 PR上强制校验。
--only只是开发便利,不是 PR 门槛
test-all.mjs 头部用大写警告写得很直白:--only只跑匹配到的tests/文件,跳过所有内联核心小节(语法、脚本、dashboard、数据契约、个人数据、路径等)。因此--only全绿 ≠ 全套通过——push 前永远要跑完整的node test-all.mjs。配套实现中,test-all.mjs还会对"无匹配文件"直接process.exit(1),让路径拼写错误永远不会悄悄把 CI 变绿。
十二、品牌、商标与许可
- 代码贡献受MITLICENSE 管辖;
- "career-ops" 名称本身受 TRADEMARK.md 管辖;
- 若你 fork 做商业用途:MIT 允许,但请给产品起你自己的名字,并遵循商标政策中关于商业命名与背书声明(endorsement claims)的规定。
结语:从文档到合并的完整闭环
最后把整条路径串起来:想方向(feature 先开 issue)→ 认领或自选(/assign,或直接对未分配 issue 提 PR)→ 判断归属(core / plugin / 独立项目,过"平行功能四问")→ 涉及新数据源先过 Source Indexing Policy 五条规则 → 遵守开发守则与红线清单 → 本地跑node test-all.mjs全绿(新测试独立成.test.mjs文件)→ 提交 PR → 通过人类 Review → 合并 → 走上贡献者阶梯。
如果遇到困惑,最有效的入口都在仓库内:开 issue、通读 docs/ARCHITECTURE.md 理解分层、对照 docs/SOURCE_INDEXING_LOG.md 看政策如何落地,或用全新的 clone 按 docs/SETUP.md 从头跑一遍再开始改代码——这既是测试你的环境,也是测试你即将贡献的项目的真实安装体验。
【免费下载链接】career-opsOpen-source AI job search: scan job portals, evaluate listings into a structured A-H report with a global 1-5 score, tailor your CV, track applications — runs locally in your AI coding CLI (Claude Code, Codex, OpenCode, Antigravity…)项目地址: https://gitcode.com/GitHub_Trending/ca/career-ops
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考