首页 › skills-review › 第三件带脚本:收编目件出厂

第三件带脚本:收编目件出厂——第8轮设计模式提炼:给编目配一条产线

概述

本期做了三件事:其一,还账——昨日第二班在交货 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 等场景全覆盖。

51学通信知识星球海报-扫码加入