Skill_Seekers Man 页面转 Skill 管线中的 “Other“ 回退分类机制解析
2026/9/23 14:26:12 网站建设 项目流程

Skill_Seekers Man 页面转 Skill 管线中的 "Other" 回退分类机制解析

【免费下载链接】Skill_SeekersConvert documentation websites, GitHub repositories, and PDFs into Claude AI skills with automatic conflict detection项目地址: https://gitcode.com/gh_mirrors/sk/Skill_Seekers

导读

本篇技术指南以 Skill_Seekers 仓库中的 golden 输出文件 other_02.md 为切入点,深入讲解man命令解析器(Man Page Scraper)将 Unix/Linux 手册页转换为 Claude AI Skill 时的分类(categorization)机制。你将理解:为什么 curl 这类不匹配任何关键字类别的手册页会落入名为 "Other" 的兜底分类、编号引用文件名(如other_02.md)如何产生、参考文件与 SKILL.md 之间的内容组织关系,以及该机制如何通过 golden 测试保证输出字节级一致。


一、other_02.md 在 Skill 产物树中的位置

other_02.md不是一篇独立文档,而是 Skill_Seekers 的 man 页面 → Skill 转换管线在关键字分类模式下生成的一类"分类参考文件"(reference file)。它以 golden 测试快照的形式存储在仓库中,对应的完整产物树如下:

tests/golden/phase2/man_kw/ ├── SKILL.md # 生成的技能主文件(含统计信息与导航) └── references/ ├── index.md # 分类索引与全部手册页清单 ├── version_control_01.md # 关键字分类:版本控制类 └── other_02.md # 关键字未命中时的兜底分类:Other

这份 golden 树由 test_phase2_golden_man.py 在"Phase 2 移植"时捕获,用于证明重构后的ManPageToSkillConverter输出与重构前逐字节一致。换句话说,other_02.md同时承担两个角色:

  1. 运行期角色:它是生成 Skill 中references/目录下"其他类"手册页的参考文档;
  2. 测试期角色:它是断言转换器输出稳定性的基准快照。

该 golden 用例的构建配置见测试第 120~128 行:man_names=["git-log", "git-diff", "curl"],显式分类为{"version_control": ["commit", "diff"]}。三个手册页中,git-loggit-diff的标题/描述命中关键字commit/diff归入 Version Control,而curl不命中任何关键字,于是落入 "Other" 兜底桶——这正是 other_02.md 存在的根因。


二、other_02.md 的文件结构逐段解析

原文件全文仅 23 行,但每一行都对应 man_scraper.py 中_generate_reference_file()的固定生成逻辑。逐段拆解如下:

2.1 一级标题:分类名

# Other

对应源码第 964 行f.write(f"# {cat_data['title']}\n\n")。这里title是分类创建时写死的"Other"(见第 889 行),而非从分类键推导(其他类别如version_control会经cat_key.replace("_", " ").title()变成 "Version Control")。

2.2 手册页二级标题:名字 + 小节号

## curl

对应第 971 行f.write(f"---\n\n## {man_name}{section_label}\n\n")。由于测试数据中 curl 的sectionNone(见测试第 72 行注释:"no section label anywhere"),section_label为空字符串,因此标题不带(1)之类的后缀;对比同目录version_control_01.md中的## git-log(1),其 section 为 1。

2.3 标题行被跳过的条件

文件里没有出现**curl - ...**加粗标题行。对应第 974~976 行:

title = page.get("title", "") if title and title != man_name: f.write(f"**{title}**\n\n")

测试数据第 73 行"title": "curl"name完全相等,因此该分支被跳过——这是一个刻意设计的边界用例,用于覆盖"标题等于名字"的生成分支。

2.4 Description(描述)

### Description Transfer a URL using one of the supported protocols.

对应第 986~992 行。curl 的描述很短,未触发 3000 字符截断;git-diff则故意构造了超过 3000 字符的描述(测试第 23 行LONG_DESCRIPTION),生成时会输出前 3000 字符并追加*... (truncated)*标记。

2.5 Options(选项)

### Options - `-s, --silent` -- Silent mode.

对应第 996~1006 行。选项以- \flag` -- 描述的格式输出;若描述超过 200 字符(如--max-count=的长描述),会截断到 200 字符并追加...(第 1004 行)。这解释了 [version_control_01.md](https://link.gitcode.com/i/85b22610c3c8cd97291a9ae88f4ae919) 中--max-count描述以...` 结尾的现象。

2.6 Examples(示例)

### Examples **Example 1:** Fetch a page ```bash curl https://example.com
对应第 1010~1019 行。示例按序号编号;若示例只有描述、没有命令(`command` 为空),则只输出描述而不输出代码块——这正是 `version_control_01.md` 中 "Prose-only example with no command" 用例覆盖的分支。 ### 2.7 缺失部分同样有意义 - **无 Synopsis**:测试数据中 curl 的 `synopsis` 为空(第 75 行),对应第 980 行 `if synopsis:` 分支跳过; - **无 See Also**:curl 的 `see_also` 为空列表(第 80 行),对应第 1022 行 `if see_also:` 分支跳过; - **无额外章节**:curl 的 `sections` 为空字典(第 77 行),因此不输出 ENVIRONMENT、NOTES 等附加小节;而 `git-log` 的 `GIT_PAGER` 环境变量章节就出现在 `version_control_01.md` 中。 --- ## 三、分类机制源码剖析:关键字匹配与 Other 兜底 `other_02.md` 的产生核心在 `categorize_content()` 方法([man_scraper.py](https://link.gitcode.com/i/f35476d411b6ee6e6c43cd8bb633317b#L836-L929))。该方法支持三种分类路径,理解它们就能解释 golden 目录中三种命名形态的差异: ### 3.1 路径一:预分组数据(不做匹配) 当 `self.categories` 的值为 `list` 且首元素为 `dict` 时(第 856~861 行),说明调用方已经完成分组,直接按组输出。这一分支面向从外部 JSON 恢复数据的场景。 ### 3.2 路径二:关键字打分匹配(other_02.md 走的就是这条) 当 `self.categories` 是 `{"类别": ["关键字", ...]}` 结构时(第 863~890 行),算法为每个手册页计算与每个类别的关键字命中分数: ```python text = page.get("description", "").lower() title = page.get("title", "").lower() for cat_key, keywords in self.categories.items(): score = sum(1 for kw in keywords if isinstance(kw, str) and (kw.lower() in text or kw.lower() in title))
  • 分数基于描述与标题中出现的关键字个数(不区分大小写);
  • 取分数最高的类别作为该页归属(max(scores, key=scores.get));
  • 没有任何类别得分时,即触发 Other 兜底
if "other" not in categorized: categorized["other"] = {"title": "Other", "pages": []} categorized["other"]["pages"].append(page)

curl 的描述 "Transfer a URL using one of the supported protocols." 与标题 "curl" 中均不含commit/diff,得分全为 0,因此进入此分支。分类键other_sanitize_filename()清洗后成为文件名前缀。

3.3 路径三:前缀自动分组(无显式分类时)

未提供categories时(第 897~924 行),按名字的-前缀分组(如git-loggit),但仅在分组确实减少类别数量时生效,否则归入单个commands类;单页场景则以页名作为类别。这就是man目录下git_01.md/curl_02.md命名的来源——注意此时curl的类别名是curl而非other,可见 "Other" 是关键字分类模式独有的回退设计。

3.4 编号文件名规则

other_02.md中的_02_generate_reference_file()决定(第 958~961 行):当总类别数 > 1 时,文件名为{cat_key}_{cat_num:02d}.mdcat_num是类别在字典中的 1 起始序号。在 index.md 的 Categories 一节中,"Other (1 man page(s))" 是第二个被写出的条目,因此得到02。单类别场景则不编号(如man_single目录下的curl.md)。


四、从分类到 Skill 的完整生成链路

other_02.md只是产物树的一部分,理解整条链路才能把握它的上下文。ManPageToSkillConverter继承自DocumentSkillBuilder,其build_skill()编排流程为:

  1. 提取(extract):通过三种策略之一获取手册页数据——调用系统man <name>命令抓取 stdout、扫描目录中的.1~.8/.man文件、或从先前保存的中间 JSON 加载(见 man_scraper.py 模块 docstring 与STANDARD_SECTIONS列表);
  2. 分类(categorize):调用上文categorize_content(),产出{"类别键": {"title": ..., "pages": [...]}}
  3. 生成参考文件:对每个类别调用_generate_reference_file(),产出references/{键}_{序号}.md,即other_02.md的诞生地;
  4. 生成索引_generate_index()产出references/index.md,其中 "Categories" 一节引用各参考文件,other_02.md被写作[Other](https://link.gitcode.com/i/a6ea401763ad2071a13696d7ed5aa41c) (1 man page(s));"All Man Pages" 一节按名字排序列出全部手册页;
  5. 生成 SKILL.md_generate_skill_md()产出技能主文件 SKILL.md,包含使用时机、常用选项摘要、示例与 SEE ALSO 导航,并在 "Documentation Statistics" 中汇总 3 个手册页、3 个选项、3 个示例与 3 个交叉引用。

由此,AI Agent 在加载该 Skill 时,可通过 SKILL.md 快速定位到references/version_control_01.mdreferences/other_02.md获取细节——"Other" 类保证了任何未分类手册页都不会丢失,只是被降级归入通用桶。


五、golden 测试如何保障输出稳定

test_phase2_golden_man.py 提供了验证other_02.md及整棵产物树的测试。其机制为:

  • 固定 fixturePAGES列表手工构造了三个手册页(第 31~83 行),刻意覆盖所有生成分支——带/无 section 标签、标题等于名字、缺失 synopsis、超长描述(>3000)、超长选项描述(>200)、纯文字示例、SEE ALSO 引用、空白额外章节(被跳过)等;
  • 三组断言test_man_prefix_grouping_matches_golden(前缀分组)、test_man_keyword_categorization_matches_golden(关键字分类 + Other 兜底)、test_man_single_page_matches_golden(单页不编号),分别对应manman_kwman_single三棵 golden 树;
  • 比对方式assert_matches_golden(build_snapshot(converter), "man_kw")将转换器实时输出快照与仓库中的 golden 树逐字节比对,任何格式漂移都会导致测试失败。

这套测试的价值在于:分类逻辑、截断阈值(3000/200/1500 字符)、文件名编号等细节一旦被无意改动,立刻会被捕获。你可以直接运行该测试验证当前实现:

python -m pytest tests/test_phase2_golden_man.py -v

六、实战:如何复现 other_02.md 的生成

要亲手得到与 golden 树一致的输出,只需构造等效配置并调用转换器。测试中的_converter辅助函数给出了最小配置模板(测试第 100~110 行):

from skill_seekers.cli.man_scraper import ManPageToSkillConverter config = { "name": "golden_man_kw", "description": "Use when testing the man golden build", "man_names": ["git-log", "git-diff", "curl"], "categories": {"version_control": ["commit", "diff"]}, "output_dir": "skill", } converter = ManPageToSkillConverter(config)

命令行等价用法(见 man_scraper.py 的 Usage 示例):

skill-seekers man --man-names git,curl --name unix-tools skill-seekers man --man-path /usr/share/man/man1 --name coreutils skill-seekers man --from-json unix-tools_extracted.json

--man-names列表中存在无法匹配--categories关键字的手册页时,产物references/中就会出现other_*.md。这一兜底设计使转换管线对任意手册页集合都保持完整性与可导航性,是 SKILL.md 中 "Total Man Pages: 3 / Other: 1 man page(s)" 统计成立的底层保证。


结语

other_02.md看似只是一份 23 行的简单参考文件,实则是 Skill_Seekers man 页面转换管线中"关键字分类 + 兜底回退 + 编号命名 + 截断保护"四大设计交汇的产物。它同时作为 golden 快照,锁定了整个分类与生成逻辑的行为契约。理解它的生成路径,也就理解了 man_scraper.py 中categorize_content()_generate_reference_file()_generate_index()_generate_skill_md()之间的协作关系,以及 golden 测试对输出稳定性的守护方式。相关实现与验证代码可继续在 man_scraper.py 与 test_phase2_golden_man.py 中深入研读。

【免费下载链接】Skill_SeekersConvert documentation websites, GitHub repositories, and PDFs into Claude AI skills with automatic conflict detection项目地址: https://gitcode.com/gh_mirrors/sk/Skill_Seekers

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询