最近在整理手头一个叫skills的个人技能管理项目。这个项目的目标很简单:把那些散落在简历、周报、项目复盘里的“我会什么”,变成一套可持续维护、可统计、可复现的技能清单。折腾完一轮之后让我意识到,很多人的问题不是没有技能,而是技能从来没有被当作数据来管理。
这篇文章会从设计思路、目录结构、命令脚本到日常使用节奏,把整个skills项目拆开讲一遍。如果你也在做个人能力管理、团队技能盘点,或者单纯想给自己建一个能坚持维护的“能力账本”,这篇文章应该能给你一套直接可抄的方案。
1. 为什么技能清单需要当成项目来管理
1.1 技能散落带来的真实问题
我先说一个自己反复遇到的场景。某次内部晋升答辩前,想整理一下过去两年到底做过什么、掌握了哪些东西。结果打开简历,发现上面写的技能还停留在两年前的水平;翻了一下聊天记录,能想起来的项目只有最近三个;再翻周报,周报写得太简略,根本看不出技术能力的成长轨迹。
这不是个例。很多人对“我会什么”的判断,依赖的是大脑的模糊记忆和临时翻找,而不是一份随时更新的数据。技能清单如果没有结构化,它就不具备积累效应——年初整理一次,年中就失效,年底又得从头来一遍。我把这个状态叫作“技能数据未资产化”。
skills项目想解决的,就是把“技能”从一个模糊概念,变成像代码一样的版本化资产:每个技能有独立的条目、有状态字段、有证据链接、有更新历史。这样不管是写简历、做绩效自评、规划学习方向,都可以直接从清单里拉数据,而不是靠回忆。
1.2 从“技能列表”到“技能数据”的转变
普通人的技能清单长什么样?大多数就是我刚说的那种:一个标题“技能清单”,下面罗列“Python、MySQL、Docker、Kubernetes、项目管理”,完事。这种列表只有名字,没有上下文。
问题很明显:同样写“Python”,我可能只是写过几个爬虫脚本,别人可能是开源项目核心维护者。没有等级、没有证据、没有使用频率,这行字在简历上毫无区分度,在个人管理上也没有指导意义。
所以skills的核心思路,是把每一条技能拆成结构化字段:
name:技能名称,机器可读category:所属领域,用于分组统计level:熟练等级,有明确定义last_used:最近使用时间,驱动“过期降级”逻辑evidences:证据列表,绑定具体项目、角色、量化结果tags:标签,用于交叉检索
这一步转变很关键。当技能变成结构化数据后,你就可以问出更多有价值的问题:我最近三个月实际在哪些技能上有产出?哪些技能写了但已经很久没用?哪个领域的积累最深?这些问题,靠一张“技能列表”永远回答不了。
1.3 方案选型:为什么是本地目录加命令行
做技能管理,市面上的选择也不少。在线知识库可以用,表格软件也可以用。但我在实际尝试后,还是选了本地目录加命令行这套组合,理由有三:
第一,数据自主可控。技能清单是高度私人的数据,放到在线平台上,免不了受格式、网络、平台策略的限制。存在本地就是纯文本文件,想怎么处理都行,换工具也不需要迁移成本。
第二,自然支持版本管理。目录即仓库,用常用的版本管理工具就能记录每次技能变更。什么时候加的、什么时候降级的、证据什么时候补充的,全都有迹可循。改错了也能回滚。
第三,自动化友好。命令行工具最擅长的事情就是批量操作和统计。每周生成技能矩阵、找出超过90天未使用的技能、导出某领域的技能清单,这些功能用脚本几分钟就能写完。在线表格虽然也能做,但每次都要手动维护,坚持成本高不少。
选型的时候我也考虑过用数据库,后来放弃了。因为技能清单的查询维度并不多,纯文本加脚本完全够用,还比数据库好备份、好查看、好迁移。杀鸡用牛刀,没必要。
2. 技能仓库的结构设计与条目规范
2.1 顶层目录划分:按领域分组,不按时间分组
skills的目录结构我做过两次调整。第一版按时间归档,比如2023/、2024/,后来发现这样根本没法用——查“数据库”技能的时候,你得翻遍所有年份的目录。第二版改成按领域分组,实际用下来顺手很多。
顶层目录设计如下:
skills/ ├── config.yaml ├── library/ │ ├── backend/ │ ├── frontend/ │ ├── devops/ │ ├── data/ │ ├── management/ │ └── soft-skills/ ├── scripts/ │ ├── add.py │ ├── review.py │ └── stats.py └── exports/ └── skill-matrix.mdlibrary/是技能条目的仓库,按领域分子目录。每个技能条目是一个独立的 Markdown 文件,文件名用全小写加连字符,例如library/backend/async-web-framework.md。
按领域分组的好处是:做技能盘点时,可以按目录直接看出自己在哪些方向有积累;新增技能时,也能很快判断该放进哪个目录、是否需要新建领域。同时,领域本身也是技能矩阵统计的行列维度。
我这里再强调一下,目录分组的粒度不要太小。刚开始可以设 6 个左右的大领域,等确实某个领域下的技能超过 10 条再拆分。如果一开始就精细化到“消息队列”“容器编排”这种程度,目录会变得很难维护,反而影响坚持。
2.2 单个技能条目的字段设计
每个技能条目就是一个 Markdown 文件,文件头部用一组结构化字段描述技能的核心信息。我现在的模板是这样的:
--- name: async-web-framework category: 后端开发 level: Proficient last_used: 2025-03-15 tags: [python, async, web, api] evidences: - project: 某内部工单系统 role: 核心开发 period: 2025-01 ~ 2025-04 detail: 负责限流与网关模块,完成链路压测与调优 --- ## 技能描述 基于异步 Web 框架搭建高并发 API 服务的能力,包括路由设计、中间件开发、数据库连接池配置、性能压测与优化。 ## 关键经验 - 用异步客户端替代同步请求,将某网关服务的 P99 延迟降低约 30%。 - 设计过一套分布式限流方案,按用户维度进行配额管理。 ## 待提升 - 对底层 ASGI 规范的理解还不够深入,需要补一下协议层的知识。这些字段的设计有一些值得说的地方。
name必须是文件名一致的机器标识,用来做去重和脚本处理。category要和目录保持对应,统计时可以快速汇总。level是核心,推荐使用三档或四档,不要搞十档,否则人根本没法准确自评。last_used是驱动“过期提醒”的关键,这个字段很重要,后面讲复盘流程时会专门展开。
evidences是整个条目里最有含金量的部分。没有证据的技能等级都是虚的。每条证据绑定一个具体项目和角色,并且尽量写量化结果——延迟降了多少、覆盖了多少场景、支撑了多大规模。写的时候不用很详细,关键是能证明“我在真实场景里用过这个技能”。
正文部分我留了三块:技能描述、关键经验和待提升。前两块是给别人看的,最后一块是给自己看的。技术文档里很容易把所有东西都写得很正面,但留一块“待提升”反而是技能清单最有价值的部分——它直接指向你的下一步学习计划。
2.3 技能等级怎么定:三档制加客观判断
技能等级是很多技能管理工具做得最失败的地方。常见的做法是 1 到 5 分打分,结果你会发现:周一打 3 分,周五心情好就变成 4 分,过两个月再看觉得应该给 2 分。分数完全看心情,没有客观依据。
我自己最后用的是三档制,每档都有明确的判定标准:
| 等级 | 判定标准 | 典型特征 |
|---|---|---|
| Familiar | 能读懂相关代码、能解决常规问题 | 看过文档、写过 Demo、改过别人代码 |
| Proficient | 独立负责过完整模块或任务 | 有真实的项目产出,踩过坑并完成交付 |
| Expert | 能向他人讲解原理并解决边缘问题 | 处理过极端性能、兼容性或架构性问题 |
注意,每一档都必须能写出对应的证据。如果写不出,那就自动降到下一档。这个规则我称为“证据驱动定级”。比如我想把“容器化部署”标成 Expert,那我的证据栏必须写得出“某服务容器迁移时解决过 PID1 僵尸进程问题”这种级别的经历。写不出来,就老实标 Proficient。
这套定级逻辑的逻辑很简单:人会系统性高估自己,但有据可查的产出不会说谎。你做过什么、解决了什么问题、交付了什么,这些是硬标准。当技能等级需要靠证据支撑而非感觉支撑时,清单的可靠性会提升一个量级。
2.4 标签与关联:让技能形成网络而不是孤岛
目录解决的是技能的分类问题,标签解决的是技能的交叉检索问题。举个例子,“数据库索引优化”这个知识点,既可以归到data/领域,也可能在某个项目中作为性能优化的一环出现过。如果只靠目录,它只能出现在一个位置;有了标签,它可以在#性能优化、#数据库、#后端等多个维度被检索到。
我建议标签保持在每条 2 到 5 个,覆盖三个方面:技术栈标签(python、redis、kafka)、场景标签(性能优化、稳定性、架构设计)、状态标签(核心技能、待提升、冷门)。
标签的价值在统计阶段才真正显现。比如我想看“所有涉及性能优化的技能”,就过滤含#性能优化标签的条目;我想准备一次架构师述职,就拉出#架构设计相关的全部技能。没有标签,这些检索就得靠目录硬翻,成本高很多。
技能条目之间的关系,也可以通过正文里的引用链接来维护。比如某个“异步框架”条目里提到“依赖扎实的 Python 基础,见python-core”,这样两个技能就关联起来了。技能清单一旦有了网络结构,它就不再是扁平列表,而是一张你专属的能力图谱。
3. 核心命令与统计脚本实现
3.1 最小可用命令集:init、add、list
直接维护一整个目录里成百上千条 Markdown 文件,靠纯手工会很痛苦,但也不需要用复杂的应用,三个命令就能覆盖日常使用的 80% 场景。
第一个是初始化命令,创建目录结构和默认配置:
python scripts/init.pyinit.py做两件事:检查library/下各领域目录是否存在,不存在就创建;同时生成一份默认的config.yaml。初始化脚本不涉及复杂逻辑,值得一说的是,默认配置里提前把review_days: 90写好了。这个值是技能过期判定的阈值,后面复盘命令会用到。
第二个是新增命令,也是最常用的命令:
python scripts/add.py async-web-framework --category backend --level Proficientadd.py的逻辑并不复杂,但要注意几个细节。name参数会转换成小写加连字符的格式;--category会校验是否在已存在的领域目录里,不在就提示可用的领域列表;--level会校验枚举值。如果条目文件已经存在,脚本会直接报错退出,避免误覆盖。
文件创建时,脚本会用配置好的模板渲染出初始内容,然后把last_used设置为当天日期。这里我踩过一个坑:最开始模板里把evidences设置为空列表,结果一年后看自己写的技能,很多条目根本没有实际的证据记录,定级也失去了意义。后来我把模板改成强制要求至少一条证据,否则不让保存。录入成本确实高了一点,但数据质量提升非常明显。
第三个是浏览命令,按目录树或按标签打印清单:
python scripts/list.py --category backend python scripts/list.py --tag performance本质上就是遍历文件、解析字段、按条件过滤,输出使用方括号标注等级和最近使用时间。语法上没难度,但有一个值得固定的格式:每行输出[等级] 技能名 (最近使用时间)。不要用表格输出,命令行场景下行式输出在终端里更好扫描。
3.2 review 命令:驱动技能淘汰与降级
技能管理最核心的机制不是记录,而是定期校准。我把这个流程封装成review.py:
python scripts/review.py --days 90这个命令会读取所有技能条目,把last_used距今超过 90 天的技能列出来,按“建议降级”“建议标注冷门”两类输出。90 天这个阈值来自一个朴素的判断:一个技能如果连续三个月没有在任何项目、练习或文档中被使用,它的熟练度大概率在退化,保留原来的 Proficient 或 Expert 等级会误导自己。
review.py的关键逻辑在如何区分“降级”和“标注冷门”。我现在的规则是:
- 超过 90 天未使用,标记为
cold,保留原等级但输出时会有提示。 - 超过 180 天未使用,且没有任何新的证据补充,自动将
level降一档。 - 降级操作不是直接改字段,而是生成一份待确认的变更建议,由我手动确认。
手动确认这一步很重要。自动化脚本应该做的是“提示风险”,而不是“替人决策”。有时候一个技能虽然最近三个月没用到,但可能正在规划下一个项目用上,那时候立即降级就不合适。脚本给出候选清单,人来做最终裁决,这个分工比较合理。
执行流程一般是这样:跑review.py之后,发现某个技能已经 200 天没有使用记录,脚本提示建议从 Proficient 降为 Familiar。我会打开这个条目,看它是否真的有半年没有证据更新。如果确实只是以前项目用过、之后再没碰过,就确认降级;如果你知道下周就要开始用,就把last_used预设为未来一个日期,并在证据里加一条“计划中”。
3.3 stats 命令:生成技能矩阵报表
checklist不只是“看单子”,更重要的是“看结构”。stats.py的功能是把所有技能条目聚合成分领域分等级的矩阵,输出一份 Markdown 报表:
python scripts/stats.py --export exports/skill-matrix.md实现思路很简单:遍历所有条目,解析category和level,按两个维度计数。核心统计逻辑大概是这样的:
from pathlib import Path import yaml, re LIBRARY = Path("library") def parse_entry(path: Path): text = path.read_text(encoding="utf-8") meta = re.search(r"^---\n(.*?)\n---", text, re.S) return yaml.safe_load(meta.group(1)) def build_matrix(): matrix = {} for path in LIBRARY.rglob("*.md"): entry = parse_entry(path) matrix.setdefault(entry["category"], {}).setdefault(entry["level"], 0) matrix[entry["category"]][entry["level"]] += 1 return matrix注意rglob("*.md")在这里比glob好用,因为领域目录是嵌套的,rglob会自动递归所有子目录。解析 Markdown front matter 用正则和yaml.safe_load就够了,不需要引入完整的 Markdown 解析库。
统计输出的结果是类似这样的矩阵:
| 领域 | Familiar | Proficient | Expert |
|---|---|---|---|
| 后端开发 | 6 | 4 | 1 |
| 前端开发 | 2 | 2 | 0 |
| 数据工程 | 3 | 3 | 1 |
| 运维与部署 | 4 | 2 | 1 |
这个矩阵的价值在两个方面。一是直观地让你看到自己在哪些领域是“广而浅”、哪些是“窄而深”。比如后端开发里 Familiar 有 6 条、Expert 只有 1 条,说明你在这个领域是广度有余、深度不足,下一步的精力应该放在把一个 Familiar 技能提升到 Proficient,而不是再新增一个 Familiar。二是可以作为季度盘点的输入,和简历、OKR 对照,看能力和目标之间的差距。
我还做了一个小的扩展:把“技能总数”和“Expert 数量”做了时间序列统计。每次跑stats.py,会在exports/history.jsonl里追加一行带日期的统计快照。这样可以看到技能总数的增长曲线,也能看到 Expert 级技能是否在稳步增加。这一点对评估个人成长很有参考意义,哪怕只是数字,也能给人一种“这个东西确实在积累”的正反馈。
3.4 与日历提醒、周报、简历导出对接
skills的脚本不止服务自己,还可以把数据输出成其他工具能用的格式。我这里做了三个对接。
第一个是日历提醒。review.py支持输出 iCalendar 格式的提醒事件,把“技能盘点”做成每季度一次的循环日程。代码上就是读取逾期技能数量,生成一个 VEVENT 块,写入.ics文件。这一步不复杂,但它解决了一个真实问题:如果没有提醒机制,任何维护类工具用着用着就会被遗忘。
第二个是周报素材导出。每周写周报的时候,最尴尬的部分是“本周产出”。其实很多产出已经在技能证据里了。我写了一个过滤逻辑:提取最近 7 天内last_used更新的条目,把对应的evidences拼成周报素材。在实现上,就是对目录做一周内的文件 mtime 过滤,再按更新时间排序输出。虽然简单,但每周能节省十几分钟的回忆时间。
第三个是简历技能栏导出。简历里的技能描述通常要求简洁,一行一个技能加一个水平标记。stats.py加一个--resume参数,把等级为 Proficient 或以上、最近 90 天使用过的技能输出为一行一个的格式。这样做还有一个好处:简历技能栏永远只呈现“高等级 + 最近在用”的技能,那些远程的、过气的技能不会混进去。
4. 日常使用流程:一套能坚持下来的节奏
4.1 事件驱动优先:项目结束时的技能操作
很多人做技能管理失败,是因为把它当成一个“每周必须打开一次”的任务。这种周期性维护很容易断档——一旦连续两周加班,整个习惯就崩了。我后来调整了策略,以事件驱动为主、周期盘点为辅。
最重要的事件节点是“项目结束”。不管项目是大是小,结束后的第二天,花十分钟把项目里用到的技能过一次。步骤是这样的:
- 打开项目代码和文档,列出实际用到的关键技术点。
- 和
skills清单里的现有条目做比对。 - 已有条目:补充新的证据,更新
last_used,并重新评估是否需要调整等级。 - 缺失条目:用
add.py录入,尽量把量化结果写进证据。
事件驱动的优势在于,技能更新的触发条件和真实工作强绑定,而不是和日历强绑定。项目结束这件事本身就是一个自然的复盘触发器,不需要额外消耗意志力。
另一个值得利用的事件节点是“完成某次重要的技术分享”或“解决一个疑难问题”。这种场景通常意味着你对某个技能的理解又深了一层,正是补充证据的好时机。我发现很多有价值的技能证据不是来自“长期稳定的项目”,而恰恰来自这些“搞定了一个有价值的问题”的事件。
4.2 每周增量录入:30 秒完成的最小动作
有人可能觉得事件驱动已经够了。但问题在于,有些技能是在项目进行中慢慢成长起来的,不是每个项目都是一结束才突然长出新技能。所以还需要一个轻量的每周动作,目标是“花 30 秒,不改大结构,只做增量”。
我给自己定的流程是:每周五下午,花不超过五分钟,回答三个问题:
- 这周有没有用到清单里已有的技能?如果用了,随便找个地方
--touch一下,更新last_used。 - 有没有发现自己新接触了什么东西?记下来,下周或者项目结束后再决定要不要正式录入。
- 有没有哪个技能这周明显感觉退步了?标记一下,季度盘点时处理。
这个流程的关键词是“轻”。不要试图在每周五完成一次完整的盘点,那太累了。每周只做增量,而且是可选的。如果这一周什么都没想起来,那就什么都不用改,也没有负罪感。维持一个最小频率的接触,让技能清单不要成为一个几个月都不打开的死数据。
我还发现一个小技巧:把add.py和touch.py的调用做成别名,缩短输入成本。比如在终端配置里加一行:alias skills-add='python ~/skills/scripts/add.py'。别看这只是省几个字符,它直接决定了你在忙碌时愿不愿意顺手录一条技能。
4.3 季度全量校准:修订等级、清理冗余
事件驱动看的是当下,周增量看的是趋势,季度校准看的是全局。每季度最后一周,我会安排一个整块时间,预计半小时,做一次完整的技能库修订。
流程分为四步:
第一步,运行review.py,处理所有逾期技能。每条逾期技能都做一次决策:确认降级、补充证据刷新、还是删除条目。删除要慎重,只有当技能已经完全不再使用且未来也不可能用时才删除。但这也是一种操作——保持库的干净比什么都留着更有价值。
第二步,跑stats.py,看技能矩阵的变化。重点关注:Expert 数量是否在期望的方向增长?有没有某个领域突然多了很多 Familiar 技能,导致矩阵看起来“虚胖”?如果虚胖了,说明最近在盲目追新,需要收敛一下学习方向。
第三步,治理 tags。每季度过一遍所有条目的标签,合并同义标签、删除使用次数为零的标签。标签是越用越有价值的东西,如果不治理,几年后就会因为标签混乱而失去检索功能。
第四步,更新config.yaml里的领域划分。如果某个领域下的技能已经超过 15 条,考虑做一级细分;如果某个领域半年都没有新增条目,考虑合并或归档。
季度校准最大的价值不是“改数据”,而是“发现趋势”。通过两个季度矩阵的对比,你能清楚地看到自己的技能结构在往哪走,有没有偏离预期的方向。这种结构性的反馈,是靠任何日常记录都无法获得的。
5. 常见问题与排查实录
5.1 中文乱码与跨平台问题
我把skills库放在多台设备间同步,持续使用几个月后,遇到了很多新手会碰到的问题——中文内容全部变成乱码。
排查后发现,根因不在数据本身,而在脚本的文件读写编码。某些旧版本脚本用默认编码写入,处理纯英文条目没问题,但一旦包含中文证据描述,在部分系统环境下就会用非 UTF-8 编码写入,导致另一个平台读取时乱码。
解决办法很简单:在所有脚本里显式声明编码:
path.write_text(content, encoding="utf-8")不要依赖系统默认编码,因为这台机器默认可能是 UTF-8,换到另一台机器就可能变成其他编码。明确写encoding="utf-8",无论什么平台行为一致。
另外如果跨平台使用,还要注意文件名大小写。我遇到过在某个系统上创建了PythonCore.md,在另一个平台同步后找不到文件。因为一条规则是文件名不区分大小写,另一条规则是区分大小写。解决的方案是做好约束:所有文件名统一小写加连字符,并且克隆仓库时先把名字拉平。这个问题排查了我整整半天,教训就是宁可命名丑一点,也不要引入大小写这种不确定因素。
5.2 技能等级虚高与“证据不可信”问题
用了skills几个月后,我发现一个微妙的问题:证据写多了之后,人会开始“表演证据”。为了匹配理想的等级,不自觉地夸大数据。比如一个只做过对接开发的技能,证据里却写着“掌握全链路技术能力”。
这个问题的根源在于“证据的可信度没有校准机制”。我后来引入了一个简单规则:证据必须能回答三个问题——用了什么技术?遇到的问题是什么?结果如何量化?如果三个问题任何一个回答不出来,这条证据不能作为定级依据。
另一个小技巧是季度盘点时对每一个 Expert 技能做一次“概念讲解测试”。随机挑两个 Expert 条目,尝试不看资料给一个非本领域的人讲清楚核心原理。讲得出来,等级保留;讲不出来,降为 Proficient。这个方法成本低,但在筛掉虚高等级上相当有效,因为它用“输出能力”检验了“输入积累”。
5.3 Markdown 存储与数据迁移问题
skills用纯 Markdown 存储一个好处是随便用什么编辑器都能打开,但真到迁移的场景就会遇到一些麻烦。比如我早期从一份 JSON 文件迁移到目录结构时,最大的坑是字段名不一致——原来的 JSON 里是last_used_time,新的 Markdown 模板里是last_used,导致统计脚本导出的数据大量为空。
解决思路是在目录结构旁边放一个迁移脚本,用核心四字段(name、category、level、last_used)做键值对齐:
def migrate_legacy(data: dict) -> dict: return { "name": data.get("skill_name", data["name"]), "category": data.get("category", "uncategorized"), "level": data.get("level", "Familiar"), "last_used": data.get("last_used_time", data["last_used"]), }字段名统一的规则是:新模板的字段名就是标准,老数据往新模板里对齐,不要反过来。如果你也在维护类似的数据结构,我建议任何字段名的变更都同步写一个迁移函数,不要手工改数据。
5.4 坚持不下来怎么办:降低门槛胜过增强动力
最后聊一个很现实的问题:再好的工具,坚持不下来就等于零。我发现很多人做技能管理,第一周兴致勃勃录了十几条,第二周到第三周开始觉得像任务,一个季度后彻底放弃。
我个人摸索出的经验是:如果觉得坚持困难,不是意志力问题,是门槛太高了。门槛高通常体现在两个地方:一是录入成本高,二是修正成本高。
录入成本高,就降低录入的触发条件。前面提到的 30 秒增量更新就是为此设计的。觉得必须写上一大段证据才能更新技能?那就只更新last_used和一句话描述,先固化“打开清单”这个动作。修正成本高,就允许自己“先记再改”,技能条目的内容错了一点、字段格式不太统一,没关系,先让它在那里,等季度校准再做整体治理。
坚持的秘诀其实是:让工具适应你的使用习惯,而不是强迫自己适应工具。我用skills三版之后才稳定下来——第一版太复杂,第二版太随意,第三版才在结构化和轻量之间找到平衡。这个工具你不会天天用,但只要在每个值得记录的时刻能顺手用一下,它就是在持续创造价值。
我在实际使用中最大的体会是,技能清单这个事的价值边界比想象中宽得多。它不只是简历的素材库,更是一个需要长期维护的能力仪表盘,你盯着它,就会慢慢发现自己的成长瓶颈在哪里、机会在哪里。后续如果继续迭代,我打算再加一个基于证据条目的技能图谱可视化,把那些通过标签和引用连起来的能力网络直接画出来,让“我会什么”这件事不再是一堆字,而是一幅真正能看出结构的地图。