<!-- title: 27万星囤件，编目格式闸交货 -->

# 27万星囤件，编目格式闸交货——第8轮设计模式提炼：给编目立一份格式合同

## 概述

本期做了三件事：其一，销账——「编目格式件」（schema 加 lint）在 09-29 挂起、今早班明确保留，本期交货，定名 `catalog-schema`；其二，补课——写件课四拍教过文字件、流程件、带脚件、验件，第五拍教的是此前从未单独成课的第五种件型：**数据格式件**，五条新套路全部围绕「怎么把一份格式写成合同」；其三，接线——上期把产线（catalog-drafter）和前台（skill-dispatcher）接成了流水线，本期在两者之间装上验收台，编目从此出厂有闸、进店有单。核心发现一句话：**27 万星的家底仓证明这个生态最不缺的是件，最缺的是件与件咬合的键——格式件不生产任何价值，它让别的件的价值可以互相结算；而写格式件的全部心法只有一句：它没有业务，它只管合身，不管好坏。**

## 一、挂账从哪来：囤件潮越涨，格式闸越缺

但囤件潮的账本背面，今天早班那卷已经替我们记了一笔：0932 卷给 27 万星家底仓的判词是「**没有维护契约**——作者搬家、改行、弃更，都不会通知你；技能之间按他个人工作流耦合，抽单件出来可能水土不服」。契约，正是这波热度里最稀缺的东西。而契约的最小单位不是什么宏大协议——是一份编目：你的件叫什么、归哪个域、用户喊哪句话能点亮它。本栏的编目工具链已经拍了三拍：09-29 的 skill-dispatcher 定了编目的用途（前台点名靠它），今早班的 catalog-drafter 定了编目的来路（扫盘产草稿），但没有一拍定过编目的**格式**——四列到底是几列、件名长什么样、缺一列算不算废行，至今全凭各卷卷面习惯。昨日卷留的挂账「编目格式件」，今早班又确认了一遍「仍空着，谁也不替谁」。本期销的就是这笔。

写件课的链条也走到了第五格：

```mermaid
flowchart TD
    A["第一拍 09-27<br/>文字件 九条套路"] --> B["第二拍 09-29<br/>流程件 六套路"]
    B --> C["第三拍 09-30早<br/>带脚件 六接缝套路"]
    C --> D["第四拍 09-30晚<br/>验件 七道闸"]
    D --> E["第五拍 本期<br/>数据格式件 五套路"]
```

读图要点：前四拍的教材全部围着「件」本身转——把话说清、把队排好、把活分出去、把闸立起来；第五拍的教材第一次围着「件与件之间的**数据**」转。这是课程里新开的一种件型，四拍里没有一拍教过它。

## 二、评测对比表：编目层装上验收台之后的地形

| 件 | 层位 | 功能 25% | 设计 25% | 实战 20% | 上手 15% | 社区 15% | 加权 |
|---|---|:---:|:---:|:---:|:---:|:---:|:---:|
| find-skills | 装前·市场发现 | ★★★★★ | ★★★★ | ★★★★★ | ★★★★★ | ★★★★★ | **4.75** |
| headcount 编制 | 装后·静态名册 | ★★★★ | ★★★☆ | ★★★ | ★★★ | ★★★★ | **3.50** |
| catalog-schema（本件） | 编目·格式验收 | ★★★ | ★★★★☆ | ★★ | ★★★★ | ★★ | **3.18** |
| catalog-drafter | 编目·草稿生产 | ★★★ | ★★★★☆ | ★★ | ★★★★ | ★★☆ | **3.10** |
| skill-lint | 全量件·出厂闸 | ★★★ | ★★★★ | ★★ | ★★★★ | ★★ | **3.05** |
| skill-dispatcher | 编目·编目消费 | ★★★ | ★★★★ | ★★ | ★★★ | ★★☆ | **2.83** |

六行口径。前五行沿用各期原值不重评（find-skills、headcount 沿用昨日卷，catalog-drafter、skill-lint 沿用今早两班，skill-dispatcher 沿用 09-29 卷）。本件为纸面分：功能三星——列数、空列、件名正则、一域上限、幽灵件名五类检查全是确定性活，纸面成立，但 v1 合同只认「竖线四列」这一种格式，格式一变全闸作废；设计四星半——只读线、从宽从严线、版本头兼容线三条线都画死在文里，且本件自己就是「只判形不判神」这条新套路的活样本；实战两星起评，未实测就是未实测；上手四星——两件套，代价是依赖 python3；社区两星，刚出生。加权 3.18，排在 drafter 之前——顺序不重要，重要的是它站的位置：**产线与前台之间，全链条此前唯一没有验收台的接缝。**

## 三、五条新套路：数据格式件的写法，特殊在哪

四拍课程教过的所有件，产出都是「动作」；格式件的产出是「结论」——每行过还是挂。产出性质变了，写法就跟着变。以下五条，每条标注它延伸或反转了哪条旧规律。

**套路一：格式件没有业务——只判形，不判神。** 分工律已立过两块地盘：判断归正文、算术归脚本（09-30 早班）；形与神的分界是它的第三块。「这个域起名叫 telecom 合不合适」「这句召唤口令顺不顺口」「这件该不该编进编目」——全是业务判断，格式件一个字都不许说；「这行是几列」「件名合不合正则」「这一域是不是超了八件」——全是形，归它管。判据照旧是 litmus：**同一个判断，换个懂业务的人来会不会得出不同结论？会的是神，不会的是形。**格式件一旦开始点评内容，它就从一个闸变成了一个唠叨的同事——而唠叨的同事的验收单，没人看第二眼。

**套路二：一行正例顶一页描述。** 规范文档最常见的死法是条款写了二十条，却没有一行「照这样长」的完整样本。v1 合同的第一条款就是一行标准正例（下文第四节原文照登），反例必须带拒绝理由——「三列，缺召唤口令」而不是「格式错误」。这是 docx 化石墙（09-28 卷判词：比一百句请小心都管用）向格式层的直接平移：**负样本教人避坑，正样本教人下笔，两样都得有，理由都得带。**

**套路三：版本进注释，不进列。** 格式件是五种件型里唯一一种「出厂即欠债」的——只要它活着，格式总有一天要升 v2，而存量数据不会自己搬家。v1 的答案是版本头 `# catalog-schema: v1`：**版本写在注释行里，不占数据列**。妙处在于向后兼容——所有旧解析器本来就跳过注释行，版本头对它们是隐形的；新解析器读头握手。数据一个字节不动，版本通道先通。这也是为什么本件对缺版本头只 remind 不 fail：存量编目没有它，从宽；新造编目建议补上，从严留给 v2 迁移日。

**套路四：报错三件套——行号、条款、改法。** 第四拍的闸立过「挂项附判据原文」的纪律，对象是整件；本套路把它下沉到行：每条挂行必须报**第几行、挂的哪条、怎么改**。校验器的输出是写给改错的人看的，不是写给日志看的——收到单的人应该不需要翻任何别的文档就能动手，这是校验类工具产出质量的全部身家。

**套路五：生成从宽，验收从严。** 接缝套路（今早班）教过进出货接口要签合同，但没教过合同两端的标准可以——也应该——**不对称**。上游 catalog-drafter 出的是草稿：描述截断到 40 字符、召唤口令是机械模板，毛边多是美德，快比干净值钱；下游本件分毫不让：四列就是四列，少一列挂。**谁从宽谁从严必须写死在两边正文里**，否则上游会为了讨好闸而提前做本该下游做的活，下游会为了体谅上游而放水——一条产线上最贵的浪费，是两个工位互相客气。

## 四、当场造：catalog-schema，编目的合同科

起名过 09-28 期的三步公式。**选骨架**：catalog 前缀家族第二名成员，名词后缀工具义（schema 即合同），与 catalog-drafter 同门不同岗。**查撞名**：本机注册表四百余件逐字扫过，catalog 前缀只有昨日构造的 catalog-drafter 一户，`catalog-schema` 无人占用，逐字可查；市场层本通道未开无法验，**上架前必查**。**留一轴**：预留 `catalog-migrate` 陪伴轴——v2 真来的那天，迁移得有人干（套路三的远期账）。

SKILL.md 全文，可直接复制：

```markdown
name: catalog-schema
description: Validate a skill catalog file against the four-column v1
  schema. Use when the user asks to check, validate, lint, or audit a
  catalog file, or reports that skill-dispatcher misreads catalog rows.
  Format-only by contract - it judges whether rows match the schema,
  never whether the catalog content is any good. Read-only by contract -
  it issues row reports and never edits files. To draft or refresh a
  catalog use catalog-drafter instead. To validate a SKILL.md file use
  skill-lint instead. Not a replacement for skill-dispatcher.
# Catalog Schema — 编目格式闸

编目的合同科：验一行四列是不是 v1 的样子。
只出验收单，不动一个字，不判内容好坏。

## v1 格式合同
- 版本头（可选，向后兼容）：`# catalog-schema: v1`，逐字一致
- 数据行：`域别名 | 件名 | 一句话触发词 | 召唤口令`，竖线分隔恰四列
- 正例（规范样本，照这行长）：
  `telecom | pcap-analyzer | 分析抓包文件 | 用 pcap-analyzer 分析这个包`
- 反例（会挂）：`telecom | pcap-analyzer | 分析抓包文件`——三列，缺召唤口令
- `#` 开头是注释行，空行忽略；一域超过八件转 remind 建议收编目
- 本合同认领的是编目工具链既有的竖线四列写法，不改一列

## 工作流程
1. 跑 scripts/validate_catalog.py <编目文件> --registry ~/.claude/skills
2. 退出码 2：文件读不了，如实报告并停
3. fail 行逐条抄单：行号、条款名、改法，一条不许漏
4. remind 项原样转呈（缺版本头、一域超八、幽灵件名），留用户裁决
5. 呈完整验收单，写盘数为零

## 不要做
- 不要替用户改一个字——闸只出单，不动刀
- 不要判内容——域起得好不好、口令顺不顺口，神归读者，形归本闸
- 不要为缺版本头挂科——从宽条款，remind 不 fail
- 不要展开读取编目里各件的正文——格式闸只认行，不认件

## 完成定义
- 每一数据行恰有一个结论（过、挂、remind 三选一）
- 每个 fail 行附行号、条款名与改法
- 本次写盘数为零
```

脚本全文（python3 标准库，零依赖，约八十行）：

```python
#!/usr/bin/env python3
"""validate_catalog.py - 只读验收编目文件是否合 v1 格式（TSV 体检单）。
契约：永不写盘；同一输入两次运行输出逐字节一致。
只判形不判神：列数、空列、件名正则、一域上限、版本头、幽灵件名。
行格式：行号 | pass/fail/remind | 条款名 | 明细与改法（行号 0 为全卷级）
退出码：0 无挂项，1 有挂项，2 文件读不了。
"""
import argparse, os, re, sys

NAME_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
VERSION_LINE = "# catalog-schema: v1"
MAX_PER_DOMAIN = 8      # 09-30 早班卷口径
COLUMNS = 4

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("catalog")
    ap.add_argument("--registry", help="注册表根目录，供幽灵件名核对")
    args = ap.parse_args()
    try:
        with open(args.catalog, encoding="utf-8") as f:
            lines = f.read().splitlines()
    except OSError as e:
        print(f"读不了 {args.catalog}: {e}", file=sys.stderr)
        sys.exit(2)

    rows, names, domains = [], {}, {}
    version_seen = False
    for no, raw in enumerate(lines, 1):
        line = raw.strip()
        if not line:
            continue
        if line.startswith("#"):
            if line == VERSION_LINE:
                version_seen = True
            elif line.startswith("# catalog-schema"):
                rows.append((no, "fail", "版本头条款",
                             f"版本头拼错；改法：逐字改为「{VERSION_LINE}」"))
            continue
        cells = [c.strip() for c in line.split("|")]
        if len(cells) != COLUMNS:
            rows.append((no, "fail", "列数条款",
                         f"{len(cells)} 列，v1 要求 {COLUMNS} 列；改法："
                         f"按「域 | 件名 | 触发词 | 口令」用竖线补齐"))
            continue
        dom, name, trig, call = cells
        for label, val in (("域别名", dom), ("件名", name),
                           ("触发词", trig), ("召唤口令", call)):
            if not val:
                rows.append((no, "fail", "空列条款",
                             f"{label} 为空；改法：补上，无内容删行不留壳"))
        if name:
            if not NAME_RE.match(name):
                rows.append((no, "fail", "件名条款",
                             f"「{name}」不合小写连字符正则；改法："
                             f"全小写，只留数字与连字符"))
            if name in names:
                rows.append((no, "fail", "同卷撞名条款",
                             f"与第 {names[name]} 行撞名「{name}」；"
                             f"改法：一行一件，重复行删除"))
            else:
                names[name] = no
            domains[dom] = domains.get(dom, 0) + 1

    if not version_seen:
        rows.insert(0, (0, "remind", "版本头条款",
                       "未见版本头；从宽不挂，建议补在文件首行，"
                       "v2 迁移那天全靠它"))
    for dom, n in sorted(domains.items()):
        if n > MAX_PER_DOMAIN:
            rows.append((0, "remind", "一域八件条款",
                         f"域「{dom}」{n} 件超上限；改法：拆域或收编目"))
    if args.registry and os.path.isdir(args.registry):
        installed = set(os.listdir(args.registry))
        for name in sorted(names):
            if name not in installed:
                rows.append((0, "remind", "幽灵件名条款",
                             f"「{name}」不在注册表；改法：确认是待装件"
                             f"还是手滑行，待装件请加注释行说明"))

    for no, verdict, clause, detail in rows:
        print(f"{no}\t{verdict}\t{clause}\t{detail}")
    hard = sum(1 for r in rows if r[1] == "fail")
    soft = sum(1 for r in rows if r[1] == "remind")
    print(f"# fail {hard} remind {soft}", file=sys.stderr)
    sys.exit(1 if hard else 0)

if __name__ == "__main__":
    main()
```

逐段注记只挑要紧的说。**描述**：触发词全是元任务词汇（check、validate、lint、audit、misreads——最后一个收的是「dispatcher 读不了我的编目」这类案发现场，验格式业务的高频入口）；双契约句 `Format-only by contract` 与 `Read-only by contract` 把套路一和只读德性提前写进门牌，让匹配层点亮它之前就知道这件只看形、不动盘；三条让路句各有主——起草归 catalog-drafter、验 SKILL.md 归 skill-lint、消费编目归 skill-dispatcher，末句与前台签互不侵犯。**正文**：v1 合同八条，第一条是版本头、第二条是正例——套路二、三直接落进了合同条款本身；「不改一列」一句是给 skill-dispatcher 的兼容承诺，存量编目一个字节不用动。**脚本**：rows 按行号自然生成、domains 与 names 都走 sorted，是字节稳定的硬底（同一文件两次运行逐字节一致）；缺版本头走 remind 不走 fail 是套路五的从宽条款；所有 fail 明细都带「改法：」前缀，是套路四的强制格式；全程只有 open 读与 print 写 stdout，只读底线未破。**诚实条款**：本脚本成稿时未实跑一次（执行通道未开），现在是纸面件；读者装好后第一件事，是拿下面安装段里那行最小正例喂它，看退出码是不是真的为零。

## 五、一次完整当班（纸面推演）

```mermaid
flowchart TD
    A["用户请求验编目"] --> B["点亮 catalog-schema"]
    B --> C["跑 validate_catalog.py"]
    C --> D{"退出码"}
    D -->|退出码2 文件读不了| E["如实报告并停"]
    D -->|退出码1 有挂行| F["逐行抄单<br/>行号条款改法"]
    D -->|退出码0| G["remind 项转呈"]
    F --> H{"remind 项在场"}
    H -->|有| I["原样列出<br/>用户裁决"]
    H -->|无| J["呈完整验收单"]
    I --> J
    G --> J
    J --> K["写盘数为零"]
```

读图要点：E、F、G 是三个分流站——文件级事故、行级挂项、全卷级提醒各走各路，但终点都汇进「呈验收单」：**格式闸的三种发现都必须通向纸面，不通向动手**。注意 I 站：remind 不是打折的 fail，它是「闸判不了、请懂业务的人来判」的正式转呈——套路一「只判形不判神」在流程图里的落点就是这座站。

接上线之后的全景：catalog-drafter 扫盘出草稿（生产，从宽）→ 用户裁断落盘（资产易主）→ **catalog-schema 验格式（验收，从严）** → skill-dispatcher 按编目点名（消费）。上期产线直通前台，中间靠信任；本期中间站有了闸，链条上每一环交出去的货，下一环都有一张单据可对。

## 六、安装与自测

```bash
# 两件套：正文、脚本
mkdir -p ~/.claude/skills/catalog-schema/scripts
# 将上文 SKILL.md 存为 ~/.claude/skills/catalog-schema/SKILL.md
# 将上文脚本   存为 ~/.claude/skills/catalog-schema/scripts/validate_catalog.py

# 出厂自测第一式：最小正例喂它，退出码应为 0
printf 'telecom | pcap-analyzer | 分析抓包文件 | 用 pcap-analyzer 分析这个包\n' > /tmp/catalog-fixture.md
python3 ~/.claude/skills/catalog-schema/scripts/validate_catalog.py /tmp/catalog-fixture.md
# 预期：一条 remind（缺版本头，行号 0），stderr 报 fail 0 remind 1

# 自测第二式：故意喂一行三列的坏行，确认「行号、条款、改法」三件套齐全
printf 'telecom | pcap-analyzer | 分析抓包文件\n' >> /tmp/catalog-fixture.md
python3 ~/.claude/skills/catalog-schema/scripts/validate_catalog.py /tmp/catalog-fixture.md --registry ~/.claude/skills
# 预期：退出码 1，第 2 行挂「列数条款」并给出补列改法

# 点火三句（应被唤起）：「帮我验一下这份编目」「dispatcher 好像读不了我的编目」
#   「这份编目格式对不对」
# 灭火三句（不应唤起）：「帮我给编目起个草」归 catalog-drafter；
#   「帮我验一件 SKILL.md」归 skill-lint；「把编目里这三件删掉」是动资产，不归闸

# 市场撞名复查（本通道今日未开，命令备上架日用）
npx skills find catalog-schema
```

九句任何一句行为不符，按 09-26 期纪律办：每轮只拧一层，先描述、后正文、脚本永远最后动——脚本错了是 bug，正文错了才是设计。

## 七、推荐排行榜

- **模式含金量王：套路一「只判形，不判神」。** 它给分工律补上了第三块地盘，也是校验类工具与「爱泼冷水的产品经理」之间的唯一界线——闸一开口点评内容，它的验收单就再没人信了。
- **最被低估的一条：套路三「版本进注释」。** 看似小技，实为所有「格式终会进化」的数据唯一无痛的升级通道；生态里凡是越攒越多的东西（编目、记忆、配置）迟早都要补这一课。
- **最省钱起步法：不装本件，把 v1 合同八条抄成肉眼清单。** 编目不过几十行的盘子，肉眼过闸一分钟；格式闸是编目长起来之后的器官，三行编目用不上它。
- **挂账更新：**「编目格式件」销账——本期交货，纸面 3.18，欢迎读者拿真编目复刻打脸；今早两班的「复检席」「点闸席」原样保留；新挂一席「迁移席」——v2 真立项时谁把存量编目搬过去，catalog-migrate 轴的预约座，现在不填。
- **新手行动清单：** 没写第一件的，回 09-27 交 minutes-fixer 的作业；写完第一件的，先过 skill-lint 七关再谈上架；手里已经有编目的，把 v1 合同八条对自家编目肉眼过一遍——**过不了第一行正例那一关的编目，dispatcher 读它全靠猜。**

## 结语

写件课拍到第五拍，五种件型集齐了：文字件教你怎么说话，流程件教你怎么点人，带脚件教你怎么把活分出去，验件教你怎么证明没写错，格式件教你怎么立合同。工厂的比喻走到今天是完整的——有车间，有前台，有产线，有质检，如今有合同科。而合同科的 Deep 之处恰在它的「无用」：**它不砌一块砖、不拧一颗螺丝，但没有它，工地上每一块砖都对不上账。**回到本周的热度侧——27 万星的家底仓证明件不缺，日榜上每小时的几十件新 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学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
