Agent Skills实战:用技能包实现git提交信息规范化
发布时间:2026/10/8 11:46:42 锦皓数字建站

你看到“skills”这个词第一反应可能是简历上那一栏“专业技能精通Office、熟悉Python”。但在过去半年里这个词在AI圈已经被玩出了完全不同的味道——把一段本来要写进系统提示词的长篇规则打包成一个可以被按需加载的独立“技能包”。我前阵子正好做了一套这样的技能包不是玩具demo是真的在每天命令行里跑的东西今天就拿其中一个最容易上手的例子——“git提交信息助手”技能包——来把这件事彻底讲透。这套技能包解决什么问题呢就是我们日常写commit message时总是一团糟“update files”“fix bug”“改了一下”。Conventional Commits规范大家都知道但人总有偷懒的时候AI在没有明确规则约束时也会偷懒。把规则固化成一个技能包之后AI会先跑脚本解析diff再套模板生成符合规范的提交信息整个过程确定又可控。适合谁看想系统学习Agent Skills怎么写、怎么调试、怎么避坑的人以及想在团队里推广“AI辅助开发规范”但不知道从哪下手的技术负责人。1. 先搞清楚Skills到底是什么为什么大家都在聊它1.1 一句话解释Agent SkillsAgent Skills更通用的说法是“技能包”本质上就是一个普通目录。目录里面放一个带有特殊frontmatter结构的Markdown说明文件主文件名一般是SKILL.md再加上若干脚本、模板、参考文档。当你向AI提的问题命中了某个技能包的描述字段时AI才会把这份说明加载进来然后按照里面定义的步骤一步步执行没命中的情况就完全不占任何上下文空间。这个机制特别好用我打个比方你就明白了。想象你刚到一家新公司人事给你一本员工手册但要求你第一天就把整本手册背下来以后每次开会、每个对话都要保持整本手册的内容在线——这是系统提示词的做法。技能包的做法是你接电话时提到“报销”前台才把报销流程那一页递给你你提到“出差”前台再给你出差制度那一页。省token是一方面更重要的是它把“行为规范”从提示词工程里捞了出来变成了一个独立的、可交付、可版本管理的产物。1.2 和系统提示词、普通prompt的核心区别我在踩坑过程中梳理过一张对比表基本能概括这几类写法的差异对比维度系统提示词普通promptAgent Skills技能包加载时机每轮请求都全量加载用户每次手动粘贴按描述匹配命中才加载上下文占用高常驻token单次高用完即弃低命中时才进入上下文可维护性改一行提交一次全量变更散落在各处无法统一管理独立目录独立版本可review可测试性难以自动化测试难以断言可对触发条件、步骤、输出做单测能跑计算/命令不行不行可以脚本是技能包一部分分享和复用复制粘贴复制粘贴打包目录即可分享这里有一个容易被忽略的关键点技能包不是“提示词的另一种写法”它把LLM的能力和外部脚本的能力拧成了一股绳。纯粹的提示词无论写得多精美都没法自己执行git diff解析文件变更而脚本可以。你让AI做的只是基于脚本的确定性输出去做决策和生成。想清楚这一层你就明白了为什么社区最近都在聊“skills”——因为它把AI从“对话工具”往前推了一步变成了一个“有操作接口的执行器”。1.3 到底什么样的任务才配得上做一个技能包不是所有任务都值得做成技能包的。我见过有人把“写情书”做成了技能包结果每封情书都一个模板味——这种开放创作类任务技能包只会帮倒忙。我自己的判定标准有这么几条至少命中两三条才值得动手规则性强、可重复执行。比如commit message、日志格式化、接口文档模板、周报生成。输入内容每次都在变但处理流程完全固定。需要确定性输出。代码评审意见、提交信息、错误分类这类场景AI自由发挥的代价很高宁可让规则说了算。涉及外部工具或文件解析。需要执行命令、读文件、算统计数据这是脚本的地盘。团队需要统一标准。当你要让十几个人产出同一风格的产物时把标准固化成技能包比发通知有效得多。记住这个筛选逻辑你后面做技能包就不会什么都想塞进去也不会做完发现根本没用。2. 动手前先设计一个技能包的完整骨架长什么样2.1 选定场景把痛点拆到可以落地我这次选择的场景是“git提交信息规范化”。写代码的人都知道好的commit message能让你三个月后回看历史时不用打开代码就知道当时干嘛了但现实是团队里大多数人提交时只会写“fix bug”运气好一点的写“修复了登录bug”没人知道修复方式是什么、影响范围在哪。我最初试图靠一段超长的系统提示词解决这个问题给模型列出所有type、scope、正文格式效果还是不行。第一是提示词太长每轮都要消耗token第二是模型经常把“读取diff”这一步跳过直接根据对话上下文猜第三是不同仓库有不同规范我得维护N个版本的提示词。后来我把这套逻辑重构为技能包让python脚本去解析diff模型只做“看脚本输出、套模板、生成文案”这件事问题才算真正解决。2.2 目录结构每个文件存在的意义都得说得清我的commit-helper技能包最终是这么组织的skills/ commit-helper/ SKILL.md scripts/ analyze_diff.py templates/ commit_subject.txt commit_body.txt references/ conventional_commits.mdSKILL.md是入口AI加载技能包时主要读这个文件scripts/analyze_diff.py负责解析git diff输出建议的提交类型和影响范围这是确定性逻辑的承载者templates/两个文件定义了subject和body的标准结构references/conventional_commits.md是参考手册里面放完整的type列表和常见scope模型只有在不确定的时候才会去查。我强烈建议目录命名全部用英文小写加连字符kebab-case。这有两层原因第一是兼容性有些工具链对中文路径、空格支持不好第二是技能包往往会进git仓库、走CI、被其它框架扫索引命名越标准被误解析的概率越低。2.3 SKILL.md的元数据description决定“什么时候被触发”frontmatter是整个技能包的门面。我先写下第一版--- name: commit-helper description: 当用户需要生成git提交信息、检查commit message规范性、或要求按Conventional Commits规范提交时使用。包含git diff解析与提交类型判定。 version: 0.2.0 ---写description的坑我踩过不止一次。第一版我写的是“这是一个git提交信息生成工具”结果模型经常在用户只是问“git怎么提交”的时候就把技能加载进来属于误触发第二版我写了“生成提交信息”结果用户说“帮我把改动提交一下”模型又迟迟不触发属于漏触发。正确的写法是描述“在什么情况下使用”而不是“这个工具是什么”。多用行为动词比如“生成”“检查”“规范化”。触发词也不要只堆名词最好把常见的用户意图写进去——“提交一下”“看看commit合规吗”“帮我写git提交信息”这些口语化表达远比一个规范名字更容易命中。3. 从零实现commit-helper技能包的完整落地过程3.1 把SKILL.md正文写成AI可执行的清单而不是百科词条写完frontmatter接下来是最关键的正文部分。我第一版犯的错误是写得太像一段教程“分析用户的git改动生成合适的提交信息”。模型读完等于没读该自由发挥还是自由发挥。后来我改成了一套带强制顺序的操作流程效果立刻不一样# Git提交信息助手 ## 输入 接受以下任一触发信号用户要求生成提交信息、要求规范commit message、或要求提交代码。 ## 执行步骤 1. 运行 git status --short 获取工作区与暂存区的变更文件列表。 2. 运行 git diff --cached --stat 获取暂存区改动的统计信息。 3. 调用 python3 scripts/analyze_diff.py获取建议的提交类型、范围和置信度。 4. 依据脚本输出的类型建议结合下面的“类型对照表”确定最终type - feat新功能、新特性 - fix缺陷修复 - docs文档变更 - refactor重构不改变外部行为 - style格式调整无逻辑变更 - test补充或修改测试 - chore构建、依赖、杂项 5. 生成subject要求 - 总长度不超过50个字符 - 使用祈使句式如“add xxx”而不是“added xxx” - 首字母大写句末不加句号 - 必须填入scope模块名取diff中变更最集中的目录名 6. 如果存在多个不相关变更按类型拆分成多条提交并逐条让用户确认。 7. 在最终回复中必须展示“检查清单” - [x] 已运行三个git命令 - [x] 已读取脚本输出 - [x] 已按规则生成subject我把这个清单贴出来是想让你看清楚一个核心原则每个步骤都以动词开头明确输入、动作、输出。不要写“分析变更”这种虚词要写“运行什么命令”“读取什么输出”“根据什么规则生成什么”。另外第7步是我调试时加进去的它让模型在输出时自带“已执行”的痕迹我能一眼看出这个技能包到底有没有被严格遵循。3.2 脚本把脏活累活干完analyze_diff.py的设计思路这个脚本负责从git diff里挖掘出结构化结论。它的输入是git diff --cached --name-status的内容输出是一段关键信息建议类型、范围、变更文件统计。核心逻辑是基于文件名和路径的启发式判断。规则不复杂但稳定且高效#!/usr/bin/env python3 Analyze git diff and suggest conventional commit type/scope. import subprocess import sys from collections import Counter TYPE_KEYWORDS { feat: [add, new, feature, implement, create], fix: [fix, bug, error, crash, wrong, hotfix], docs: [readme, doc, comment, guide, md], refactor: [refactor, rename, move, clean], style: [format, whitespace, indent, lint], test: [test, spec, fixture, jest, pytest], } def get_staged_files(): out subprocess.check_output( [git, diff, --cached, --name-status], textTrue, errorsreplace, ) return [line for line in out.strip().splitlines() if line] def suggest_type(files): score Counter() for line in files: status line[0] path line.split(\t)[1] if \t in line else line[1:] if status D: score[fix] 1 continue path_lower path.lower() for t, kws in TYPE_KEYWORDS.items(): if any(k in path_lower for k in kws): score[t] 1 if . not in path_lower.split(/)[-1]: score[chore] 1 if not score: return chore return score.most_common(1)[0][0] def main(): files get_staged_files() if not files: print(暂无暂存区的变更请先执行 git add) sys.exit(0) type_ suggest_type(files) # 简单scope取变更最集中的一级目录名 scopes Counter() for line in files: path line.split(\t)[1] if \t in line else line[1:] parts path.split(/) if len(parts) 1: scopes[parts[0]] 1 else: scopes[root] 1 scope scopes.most_common(1)[0][0] print(f建议类型: {type_}) print(f建议scope: {scope}) print(f变更文件数: {len(files)}) if __name__ __main__: main()这段代码有几个值得注意的细节。第一命名强制--cached只分析暂存区的变更因为提交信息只应该关心它。第二对“删除文件”我直接赋予fix类型——删文件往往意味着清理或修复某个导致问题的资源这个启发式判断在多数仓库里是成立的。第三scope取的是“变更最集中的一级目录”这比让模型自己猜可靠得多模型拿到的不是“app/controllers/user_controller.rb”这种啰嗦信息而是一个干净的“app”。脚本的价值在于剔除不确定性。规则写在SKILL.md正文里不同模型可能理解不一致规则写在python里所有人、所有模型拿到的都是同一个答案。这也是我对“skills”这个方向最大的体会能让脚本做的事就别让模型自由发挥。3.3 模板和参考文档让产出格式也变成一种约束模板文件不复杂但它的存在让输出格式不再是AI临时决定的。commit_subject.txt内容{type}({scope}): {subject}commit_body.txt内容{type}({scope}): {subject} - 变更点1 - 变更点2 Refs: issue-#{issue_id}为什么模板单独拆成文件而不写在SKILL.md里因为后期我想扩展一个场景把模板里的subject和body用于生成git提交时的-m参数拆分文件后脚本可以直接读取模板拼装。技能包做到后面你会发现“资源文件”和“说明文件”分开能省很多事别图省事全堆在一个Markdown里。references/conventional_commits.md我放的是完整的type表、每个type的使用样例、以及“什么情况别用这个type”。比如我会在里面写不要用docs去描述“修复了文档里错的API地址”那其实是fix不要用refactor去描述“定义了新变量”那是feat。让模型在生成前快速查一遍这个表比让它靠记忆强。3.4 挂载、调用与验证从“能用”到“好用”技能包的挂载方式因框架而异但通用逻辑是告诉框架“你的技能目录在哪”。我先创建目录结构并放入文件mkdir -p skills/commit-helper/{scripts,templates,references} # 放入SKILL.md、analyze_diff.py、模板文件和参考文档 # 然后在你的agent配置中把技能根目录指向 ./skills 即可挂载完第一件事不是直接聊天而是先做一次本地冒烟测试。我会造一个包含新增文件、修改文件、删除文件的暂存区然后调用技能包生成提交信息。实测产出类似feat(cli): add format command for output alignment - 新增 --format 参数支持表格输出 - 调整列宽计算逻辑避免中文对齐异常这个输出就可以直接git commit -F使用了。从这一步开始我基本不再手写commit message遇到“把改动提交一下”的需求直接让AI调用技能包几秒钟出结果不满意就让它改style。4. 调试和排查技能包翻车实录4.1 技能没被触发问题多半出在description这是技能包最常见的翻车现场。用户说“帮我把代码整理一下提交了”结果AI没加载技能包而是把它当成普通对话处理最后回了一段“建议你执行git add……”的废话。排查思路很简单先看description里有没有覆盖用户这句话里的行为词。用户说的是“提交”description里如果只写了“生成git提交信息”模型就很可能不认为这是在请求生成提交信息。我的解决办法是给description加一层“触发场景枚举”description: 当用户要求提交代码、生成commit message、检查commit规范性、或出现“提交一下”“写个commit”“看看怎么提交”等意图时使用。枚举触发场景时不用怕啰嗦但要防过度触发。我见过有人把description写成“处理与git相关的所有问题”结果用户问“git怎么回退版本”也加载了技能包然后技能包强行走“生成提交信息”的流程闹出大笑话。原则是宁可窄不可宽。窄了最多漏触发宽了会误触发误触发比漏触发难排查得多。4.2 模型不按流程走如何把指令从“建议”变成“强制”另一个高频问题是模型看了SKILL.md但它不执行“运行脚本”这一条直接凭空生成submit message还编得有模有样。这是LLM的通病——它更喜欢“直接产出”而不是“先调用工具再产出”。对治的办法有三个我反复调优后认为都有效建议叠加使用。第一把“运行脚本”提为第0步并且用加粗标记告诉模型“在我们拿到脚本输出之前禁止生成任何提交信息”。第二在SKILL.md里加一句“本技能定义的流程优先于模型默认行为”这句话还真能提高不少遵循率。第三也是最有用的要求模型在最终回复中附上“检查清单”就像前面第7步那样一旦模型知道自己要在输出里交代执行过程它就不太敢跳步了。4.3 脚本环境问题路径、依赖、编码脚本跑不起来再好的设计也白搭。我在Windows和macOS两个环境都踩过坑总结成几个要点。路径得用相对当前脚本文件的定位方式别写死绝对路径。SKILL.md中引用脚本时我已经用scripts/analyze_diff.py这种相对路径了脚本内部如果有子模块也用Path(__file__).parent来推导基础目录这样整个技能包在任意文件夹解压都能跑。编码是大坑。Windows下subprocess默认返回bytes且中文文件名经常触发gbk/utf-8混乱。我在代码里用了textTrue加上errorsreplace基本能挡住绝大多数编码异常。如果你们的仓库里还有更生僻的文件名可以再考虑在SKILL.md的“环境要求”一节注明“运行于python3.9建议使用UTF-8终端”给使用方一个明确预期。依赖要克制到最少。我的脚本只依赖python标准库和git命令本身没有引入第三方包。任何第三方依赖都会让技能包的分发成本上升一个量级团队里别人一装就跑不起来。能用标准库解决的问题绝不上依赖。4.4 多技能协作时的冲突与隔离当你的skills目录里不只一个技能包时冲突就来了。我后来做了“文档排版校验”技能它也宣称处理“docs”类型结果有一次同时触发两个技能模型分身乏术一份输出里混合了两套规则。解法分两层。第一层是description边界写清楚每个技能的description都加上“本技能不处理XXX情况”。比如commit-helper里写明“只处理与git提交信息相关的内容不负责文档排版规范”这能显著降低并发触发概率。第二层是在SKILL.md里增加一个字段声明优先级--- name: commit-helper description: ... priority: high conflicts: docs-formatter ---当框架读到两个技能都命中时优先级高的先执行冲突技能的规则明确不采用。这属于进阶玩法但如果你要把技能包铺开给团队用建议从一开始就留出这个字段位别等冲突爆炸了再返工。5. 技能包设计的三条原则也是我踩坑后的心得5.1 能写进脚本的规则就绝不留白给模型我做的这些技能包里凡是“确定性逻辑”最终都下沉到了脚本commit类型判定、scope提取、diff统计全是代码算出来的。SKILL.md只保留两样东西操作流程和决策参考。为什么因为模型在“确定性判断”上不可靠但在“基于确定结果做表达”上非常强。让AI干它擅长的活把脏活累活交给代码这是我做完四五个技能包后最深的感触。5.2 技能包要当产品维护不是当提示词随手扔大多数人的技能包活不过一周是因为它没有版本管理、没有changelog、没有测试样例。我现在每个技能包都紧跟git仓库frontmatter里的version字段与tag对齐SKILL.md的正文变更必须附带更新说明还会在tests/目录下放3-5个输入样例和期望输出。这些做法听着繁琐但好处是团队里其他人接手时能快速理解这玩意的边界和用法而不是靠群聊里翻聊天记录。5.3 先保证稳定触发再去追求生成质量技能包迭代的顺序是先保证该触发的时候必触发、不该触发的时候不触发再看产出内容质量。很多人一上来就打磨SKILL.md里的措辞结果发现技能包根本没被加载白费功夫。我的调试顺序永远是description触发测试、流程遵循度测试、输出质量测试、跨模型一致性测试一步步来每一步过了再进下一步。这套commit-helper技能包后来我还扩展出了changelog生成器、PR描述生成器最后直接接到CI里每次PR都会自动检查commit message合规性不合规就由机器人评论提示。从那以后代码评审时再也没人追着问“这句commit到底改了什么”。“skills”这个方向真正的价值不在于你写了多少花哨的规则而在于你把它变成了一个可测试、可复用、可协作的工程产物。如果你也想做一个技能包我建议从“你天天要做的重复性规范任务”入手比如commit message、周报模板、接口文档格式。选定一个场景按我上面的思路搭出目录写上SKILL.md跑通一次你就能彻底理解。最后分享一个具体的小技巧给SKILL.md正文里每个步骤都设定“可验证的输出物”。比如“运行命令之后你会拿到一份脚本输出”这就是这个步骤的输出物。步骤是否有输出物是判断它写没写到位的最快标准。没有输出物的步骤多半是废话删掉它。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。