本期做了三件事:其一,还账——昨日第二班在交货 skill-dispatcher 时挂了两席,本期销掉第二席「收编目件」;其二,补课——本栏的写件课程已有第一件(minutes-fixer,纯 Markdown)和第二件(skill-dispatcher,纯流程),第三件第一次自带脚本,带脚件的写法在九条套路、六条前台套路里始终是空白,本期补上六条新套路;其三,接线——产线件(生产编目)与前台件(消费编目)当场接成一条完整流水线。核心发现一句话:前两件教的都是「怎么把话写清楚」,第三件要教的是「哪些话根本不该由模型来说」——判断归正文,算术归脚本,这一刀切下去,SKILL.md 的写法从散文课变成了分工课。
本周这条课线已经拍了两拍。第一拍是 09-27 的九条套路卷:样本件 minutes-fixer,教你把一件事写清楚——描述写何时用、正文是菜单、负面清单、完成定义,九条套路全部围绕「文字件」。第二拍是昨日的编制路由件卷:skill-dispatcher,教你让一堆件排好队——先判域、再点名、后交棒,六条套路全部围绕「流程件」。但昨日卷留了一句「新挂两席,都小,都空着」:①「编目格式件」,把一行四列的编目 schema 做成可校验的格式;②「收编目件」——「套路六的执行者,扫描注册表自动起草编目初稿」。本期交的就是②。
为什么②比①急?看这个链条就明白:
flowchart TD
A["第一件 minutes-fixer<br/>把一件事写清楚"] --> B["第二件 skill-dispatcher<br/>让一堆件排好队"]
B --> C["第三件 catalog-drafter<br/>给编目配一条产线"]
skill-dispatcher 的全部食物是 references/catalog.md,而那份编目按昨日的规矩是「用户的资产」——意味着每个装了前台件的人,都要手工把装机盘里三三百八十余件(本栏口径 384 到 400 余件两次在档)逐个抄成一行四列。前台件越有用,手工编目就越成一个瓶颈。收编目件就是把这道手工工序变成一条产线:脚本负责扫,模型负责归域,用户负责拍板。
| 件 | 层位 | 功能 25% | 设计 25% | 实战 20% | 上手 15% | 社区 15% | 加权 |
|---|---|---|---|---|---|---|---|
| find-skills | 装前·市场发现 | ★★★★★ | ★★★★ | ★★★★★ | ★★★★★ | ★★★★★ | 4.75 |
| headcount 编制 | 装后·静态名册 | ★★★★ | ★★★☆ | ★★★ | ★★★ | ★★★★ | 3.50 |
| catalog-drafter(本件) | 装后·编目生产 | ★★★ | ★★★★☆ | ★★ | ★★★★ | ★★☆ | 3.10 |
| skill-dispatcher | 装后·编目消费 | ★★★ | ★★★★ | ★★ | ★★★ | ★★☆ | 2.83 |
四行口径。其一,find-skills 与 headcount 两行沿用昨日卷原值,不重评;headcount 星数按今日快照更新为 1,715(昨日卷记 1,700,跨快照自然漂移)。其二,本件是纸面分:功能三星——扫描、截断、撞户上报是确定性活,纸面成立,但归域质量全押在模型身上;设计四星半——分工线、只读线、降级线三线都画死在文里;实战两星起评,未实测就是未实测;上手四星——两步装,代价是依赖 python3;社区一星半,沿用昨日「刚出生不给满扣」的口径。加权 3.10,纸面上它排在 find-skills 之后、昨日件之前——这不重要,重要的是它让昨日那 2.83 有了吃饭的家伙。
九条套路卷的套路四只说了一句「能写脚本就别写散文」,没说搬过去之后两边的接口怎么签。带脚件的全部新问题都长在这条接缝上。以下六条,每条标注它延伸或反转了哪条旧规律。
套路一:判断归正文,算术归脚本。 09-25 期把分工律定为「Markdown 管协议、代码管运输」,本期给它一个可操作的判据:同一个输入必须得到同一个输出的,进脚本;同一输入可以合理产生不同输出的,留正文。扫描目录、数行数、截断描述、按字典序排队——这些活模型干十次会出十种顺序,脚本干一万次都是一个答案;而「这堆件该归哪个域」「召唤口令怎么说人话」——这些活写进脚本就是把判断焊死成模板。一条 litmus:你写不写出它的伪代码?写得出的是算术,写不出的才是判断。
套路二:脚本是进货,正文是出货。 脚本的 stdout 不是给用户看的产品,是给模型吃的原料。所以输出要有契约:列固定、分隔符固定、截断规则写死在脚本里而不是留给模型临场发挥。本件契约定为 TSV 三列(件名、描述摘要、召唤口令草稿),摘要默认 40 字符截断——模型拿到的是可以逐列解析的行,不是一段要靠猜的散文。契约签得越死,正文里「怎么解读脚本输出」要写的话就越少,省下来的都是上下文预算。
套路三:只读是脚本的默认德性。 昨日卷立过「编目是用户的资产,你有权建议维护,无权擅自改写」;本期把这条负向边界从描述下沉到脚本:凡是以注册表、配置、编目为对象的脚本,默认只读——产出草稿打印到 stdout,落盘永远过用户的手。理由不只是安全:只读的脚本才可能被反复重跑、被拿去对账、被当作注册表的量尺。一旦它有写权限,每次运行都是一次状态变更,产线就变成了黑箱。
套路四:降级三条路写进正文。 09-26 期的维修清单(不触发查名字、乱触发加否定句)是出事之后的动作;带脚本把它前置到出厂——正文里必须写死三条既定降级路径:工具不在(python3 缺席时退回手工法,ls 加逐个 head,不许现场造轮子)、扫描为空(大概率是 root 传错,报错并请用户确认目录,不许静默交白卷)、同名撞户(skill-creator 三户同名的旧案就在注册表里躺着,撞户行原样上报,合并与否用户裁)。每条可预见的失败都有一个写好的动作,模型才不会在出错时即兴发挥。
套路五:先跑便宜的,再读贵的。 渐进式披露管的是加载分层(描述常驻、正文按需、深料再按需),带脚本把它延伸到执行顺序:扫全部件名是一毛钱的活(一条脚本),读任何一件的正文是一块钱的活(一次展开)。流程必须先跑便宜的一遍,拿到全量清单之后再问:哪些件值得展开读?本件的答案是不读——编目草稿只需要门牌(名字加描述),一个正文都不用展开。流程里每一处「读取」都要有理由,便宜的活永远排在前面。
套路六:字节稳定才算交货。 九条套路卷的完成定义管住了「范围」(每条信息都处理了吗),管不住「字节」——散文件的十次执行就是十种措辞。脚本的完成定义硬得多:同一注册表状态,两次运行输出逐字节一致。不写时间戳、不打乱顺序、排序规则写死。这不是洁癖:字节稳定的输出才能 diff,能 diff 的编目才能对账,能对账的产线才配叫产线——否则它只是个说话好听的手工作坊。
起名过一遍 09-28 期的三步公式。选骨架:裸名工具式,名词加 er 尾工具义(jevgrep 同款语法),定名 catalog-drafter。查撞名:本机注册表四百余件逐字扫过,无任何 catalog 前缀件;市场层本通道未开无法验,上架前必查,真撞了按公式换轴(-tools、-cli、加作者前缀)。留一轴:预留 -test 陪伴轴(pptx 家族旁住着 pptx-test 的先例)与 -sync(将来与旧编目做双向同步时用)。
SKILL.md 全文,可直接复制:
name: catalog-drafter
description: Draft a starter catalog for skill-dispatcher by scanning the
local skill registry. Use when the user asks to draft, bootstrap, refresh,
or audit a skill catalog, or asks what skills are installed and how each
is called. Read-only by contract - it proposes catalog rows and never
writes files. For finding new skills to install use find-skills instead.
Not a replacement for skill-dispatcher or any catalogued skill.
# Catalog Drafter — 编目产线
装机盘的第三层:扫出全部在册件,起草一行四列的编目,落盘交给用户。
本件只生产草稿,不装货。
## 编目格式
四列规格从 skill-dispatcher 的 references/catalog.md 采认:
域别名 | 件名 | 一句话触发词 | 召唤口令
其中域别名与召唤口令由你定稿,脚本只供后两列原料。
## 工作流程
1. 盘点:运行 scripts/draft_catalog.py;退出码非零走降级路径,
不许现场改脚本
2. 归域:给每行补域别名;一域超过八件时,先建议收编目再交稿
3. 撞户上报:标记 [DUPLICATE] 的行原样列出,禁止擅自合并,
留哪一户由用户裁决
4. 呈稿:输出完整四列表;召唤口令列由脚本草稿改写成
用户可原话复制的自然句
5. 交割:用户确认后,由用户自行把表贴进前台件的编目;本件不落盘
## 不要做
- 不要写任何文件——产线只出厂,不安装
- 不要推荐脚本输出里不存在的件名
- 不要静默合并撞户行
- 不要为「补全描述」去展开读取件的正文——编目只认门牌
## 示例
- 「我的装机盘里都有什么」→ 跑脚本,按域分组呈全表,
总件数与撞户数随表报
- 「给 skill-dispatcher 刷新编目」→ 呈四列表,并逐行标注
与旧编目的增减
- 「帮我找几个能处理 PDF 的新件」→ 让路 find-skills,
产线不开进货单
## 完成定义
- 呈出的表恰好四列,每行件名都出自脚本输出
- 脚本退出码、撞户数量如实随稿报告
- 本次运行写盘数为零
脚本全文(python3 标准库,零依赖,约六十行):
#!/usr/bin/env python3
"""draft_catalog.py - 只读扫描技能注册表,输出编目草稿原料(TSV)。
契约:本脚本永不写盘;同一注册表状态两次运行输出逐字节一致。
输出列:件名 | 描述摘要 | 召唤口令草稿(前两列给机器,第三列供改写)
"""
import argparse, os, re, sys
def parse_frontmatter(path):
try:
with open(path, encoding="utf-8") as f:
head = f.read(4096)
except OSError:
return None, ""
m = re.match(r"^---\s*\n(.*?)\n---\s*(\n|$)", head, re.S)
if not m:
return None, ""
name, desc_lines, in_desc = None, [], False
for raw in m.group(1).splitlines():
line = raw.rstrip()
nm = re.match(r"^name:\s*(.+?)\s*$", line)
dm = re.match(r"^description:(.*)$", line)
if nm and not in_desc:
name = nm.group(1).strip().strip("'\"")
elif dm:
in_desc = True
first = dm.group(1).strip()
if first:
desc_lines.append(first)
elif in_desc and raw[:1] in (" ", "\t") and line:
desc_lines.append(line)
elif line and line[:1] not in (" ", "\t", "#"):
in_desc = False
return name, " ".join(desc_lines)
def scan(root):
rows = []
for d in sorted(os.listdir(root)):
p = os.path.join(root, d, "SKILL.md")
if os.path.isfile(p):
name, desc = parse_frontmatter(p)
rows.append((name or d, d, desc))
return rows
def clip(text, width):
text = re.sub(r"\s+", " ", text or "").strip()
return text[: width - 1] + "…" if len(text) > width else text
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--root", action="append",
help="技能根目录,可多次传入;缺省扫用户级与项目级")
ap.add_argument("--max-desc", type=int, default=40)
args = ap.parse_args()
roots = args.root or [os.path.expanduser("~/.claude/skills"),
os.path.join(os.getcwd(), ".claude", "skills")]
all_rows = []
for root in roots:
if not os.path.isdir(root):
print(f"ERROR: root not found: {root}", file=sys.stderr)
sys.exit(1)
all_rows.extend(scan(root))
if not all_rows:
print("EMPTY: no SKILL.md found", file=sys.stderr)
sys.exit(2)
seen = {}
for name, _d, _desc in all_rows:
seen[name] = seen.get(name, 0) + 1
dupes = {n for n, c in seen.items() if c > 1}
for name, d, desc in sorted(all_rows): # 固定排序,保证可重放
mark = " [DUPLICATE]" if name in dupes else ""
call = f"用 {name} 来 {clip(desc, 18) or '……'}"
print(f"{name}\t{clip(desc, args.max_desc)}{mark}\t{call}")
if dupes:
print(f"# 撞户 {len(dupes)} 个:{', '.join(sorted(dupes))}",
file=sys.stderr)
sys.exit(0)
if __name__ == "__main__":
main()
逐段注记只挑要紧的说。描述:触发词全部是元任务词汇(draft、bootstrap、refresh、audit、catalog),业务域名词零投放——这是昨日卷套路三「投票权自我设限」的直接沿用;Read-only by contract 一句把只读德性写进了描述本身,让匹配层在点亮它之前就知道这件只动嘴;负向两连都有主:use find-skills instead 是指名让路,Not a replacement for skill-dispatcher 是给上下游同行签的互不侵犯条约。正文:流程五步里最重的是第 4 步的分工说明——脚本给的召唤口令草稿(「用 X 来……」)是机械模板,必须由模型改写成自然句,这正是套路二的进出货接口:脚本出货是原料,正文出货才是产品。脚本:sorted() 写死是套路六的字节稳定;sys.exit(2) 对应空白扫描的降级路是套路四;全程只有 open(path) 读和 print 写 stdout,是套路三的只读底线。诚实条款:本脚本在本期成稿时未实跑过一次(执行通道未放行),它现在是纸面件——读者装好后第一件事应当是裸跑一遍自测,这正是下面的出厂流程。
flowchart TD
A["用户请求编目草稿"] --> B["点亮 catalog-drafter"]
B --> C["跑 draft_catalog.py"]
C --> D{"退出码为零"}
D -->|非零| E["走三条降级路<br/>如实上报"]
D -->|零| F["拿到 TSV 原料"]
F --> G["归域补别名<br/>改写召唤口令"]
G --> H{"遇到 DUPLICATE 行"}
H -->|有| I["原样列出<br/>用户裁决"]
H -->|无| J["呈完整四列表"]
I --> J
J --> K["用户确认后<br/>自行贴进编目"]
K --> L["本次写盘数为零"]
读图要点:E 和 I 是全图仅有的两个分流站,一个管脚本失败、一个管数据撞车,殊途同归地汇进「呈稿」——产线的两种意外都必须通向报告,而不是通向沉默或擅自处置。另外注意 L:它不是流程步骤,是全程纪律,写进完成定义里让每一班都自我验收。
接上线之后的全景是:catalog-drafter 扫盘出草稿(产线)→ 用户裁断落盘(资产易主)→ skill-dispatcher 按编目点名(前台)→ minutes-fixer 之流的日常件干活(车间)。三件课就此闭环:有人进货,有人看店,有人做活。
点火三句——应被唤起:①「帮我给编目起个草」②「我的装机盘里都有什么」③「给 skill-dispatcher 刷新编目」。
灭火三句——不应唤起:①「帮我找个能处理 PDF 的新件」→ find-skills 的进货业务;②「用 pcap-analyzer 分析这个包」→ 被调度件直接接活,前台与产线都闭嘴;③「把编目里这三件删掉」→ 产线不动存量资产,只出建议清单等用户自己动手。
九句任何一句行为不符,按 09-26 期纪律办:每轮只拧一层,先描述、后正文、脚本永远最后动——脚本是确定性部件,它错了是 bug,正文错了才是设计。
# 三件套:正文、脚本、无需编目(编目是产出不是输入)
mkdir -p ~/.claude/skills/catalog-drafter/scripts
# 将上文 SKILL.md 存为 ~/.claude/skills/catalog-drafter/SKILL.md
# 将上文脚本 存为 ~/.claude/skills/catalog-drafter/scripts/draft_catalog.py
# 项目级随 git 走(团队共享同一份注册表口径时)
mkdir -p .claude/skills && cp -r ~/.claude/skills/catalog-drafter .claude/skills/
# 出厂自测:裸跑脚本,先验只读、再验可重放(连跑两次 diff 输出)
python3 ~/.claude/skills/catalog-drafter/scripts/draft_catalog.py --root ~/.claude/skills
# 上下游市场撞名复查(本通道今日未开,命令备上架日用)
npx skills find catalog-drafter
本期的真发现不在某一件脚本,而在一条界线:SKILL.md 写到第三件,难点从「把话写清楚」变成了「把话让出去」——凡是算术,让给脚本;凡是货权,让给用户;凡是意外,让给降级路径;凡是稳定,让给字节。第一件教你怎么说话,第二件教你怎么点人,第三件教你怎么闭嘴把活分出去。三件都会写了,装机盘才真正从堆放间变成工厂:有进料的产线,有调度的前台,有干活的工位——而写件的人,终于可以从车间主任升任厂长,只管定规矩,不管开机器。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
