# 技能重组 · 千行技能拆分(SKILL.md 主干 + references/ 详情档) > **归属**:技能 `dsh-knowledge-upkeep` 的详情档(**按需读**,不是每次都要读)。 > **本档覆盖**:§10 的执行清单 · 通用拆分器用法 · 2026-09-22 首次实战的实测数据与决策记录 · 坑。 > **主文件 / 判据** = `../SKILL.md`(§2 分层判据 · §8.6 注入预算 · §9 技能集体检 · **§10 技能重组**)。 > **来源**:2026-09-22「技能重组线」首跑后沉淀(首次对象 = `dsh-change-workflow` / `dsh-opensource-release`)。 --- ## 1. 为什么要有它(一句话) 技能长到千行级 ⇒ **每次加载都全量注入**,而里面大半是长表 / 实测记录 / 历史细节。拆成「主干 + 详情档」不是审美,是**把注入预算还给判据**(同 §8.6:长文件后半段等于不存在)。 **与相邻机制的分工**: | 机制 | 管什么 | |---|---| | §9 技能集体检 | 技能**挑不挑得中**(description 区分度) | | §10 技能重组 | 技能**加载后吃多少上下文 / 判据找不找得到**(文件内部结构) | | `agent-operating-rules §10.1` | 要不要把多个技能**合并** | | `dsh-architecture-lifecycle` | 架构**过程文档 vs 定稿**的分层 | --- ## 2. 通用拆分器 `07-scripts/split_skill.py` ```bash 07-scripts/split_skill.py --spec ``` **规格格式**(照抄改): ```json { "skills_dir": "E:/ProgramData/.workbuddy/skills", "backup_dir": "E:/ProgramData/AI技能/aliyun-dsh-server/tmp/_keep-20260922/split-bak", "skill": "dsh-change-workflow", "main_desc": "§1 六阶段流程 · §2 红线速查 · §3 Git Bash 坑", "moves": [ { "ranges": [[14, 92]], "ref": "00-平台速查.md", "title": "平台速查(硬编码事实,勿猜)", "covers": ["平台速查(硬编码事实,勿猜)"], "pointer": "> 📂 **平台速查表已下沉** → `references/00-平台速查.md`" } ] } ``` **要点**: - `ranges` = **1-based 闭区间**,可多段(`[[481,504],[555,600]]`),**不得重叠**;一段一个 `ref`。 - `pointer` = 插在该组区间**起点位置**的指针行(可多行用 `\n`);**要点名该档覆盖的章节**,否则违反验收 ③。 - `covers` = 原章节名,用于生成主干的「详情档 × 覆盖的原章节 × 原行段」表(**跨档引用的定位表**)。 - `--dry-run` 只看校验,不落盘。 **脚本内置自校验**(任一 FAIL 就必须停下来看,别硬推): 1. **行数守恒**:`主干内容行 + 详情行数 == 原行数`; 2. **逐行包含**:`Counter(原行) <= Counter(主干非指针行 + 全部详情行)` ⇒ **丢失 0**; 3. **节可寻址**:原文件每个 `^#{1,3} ` 标题文本都能在新结构(主干 + 任一详情档)里找到; 4. **frontmatter**:`version:` 行唯一且值不变。 **幂等**:脚本永远以**备份**为拆前基准 ⇒ 反复跑不会二次切割(⚠️ 首次失误版本读的是活文件,第二次跑就 `AssertionError` —— 这个坑已修,别再写回活文件)。 --- ## 3. 2026-09-22 首跑实测数据(两个对象) | 技能 | 原 SKILL.md | 主干 | 详情档 | ≤350 目标 | |---|---|---|---|---| | `dsh-change-workflow` | 1052 行 | **319 行** | 9 档 / 867 行 | ✅ 达标 | | `dsh-opensource-release` | 1089 行 | **534 行** | 5 档 / 629 行 | ❌ 多 184 行 | - **守恒实测**:`275 + 777 = 1052`|`510 + 579 = 1089`(逐行比对丢失 0;新增的 34 / 19 行全是指针与索引行)。 - **可寻址实测**:原 51 / 68 个标题,未命中 **0**。 - **三处对账**:16 个文件(2 主干 + 14 详情档)三方 md5 全一致。 **为什么 opensource-release 到不了 350(决策记录)**:它的「不看会违规/事故」常驻项合计约 **453 行** —— 硬规则 R-O1–R-O17 **239** + 事故清单 40 + 事实 34 + 硬规则总表 23 + 用户当场纠正口径 16 + 授权结构 35 + 验证八件套 37 + SOP 主干 29。压到 350 的唯一路径是把 R-O 硬规则降级成指针 ⇒ 违背「任何会话不得放宽」。**当时的选择 = 判据常驻优先,如实报告数字**,没有为凑行数动硬规则(对应用户「确保功能效果不受影响」的硬约束)。 --- ## 4. 断头引用:怎么扫、怎么修 **扫**(正则挑跨节引用,逐行打印,人工过一遍): ``` 见上文|见下文|见前文|见后文|见上表|见下表|见上面|见下面|上文|下文|见 §|见§|参见|详见|见阶段|见坑|见附 ``` **实测命中 5 处**(目标全在详情档里):`见下表:dsh-univer-office`(→ 保留/移除档)· `见 §8 坑 9`、`见坑 36`(→ 实测坑档)· 详情档之间的 `见 §8 坑`、`见坑 31–36`。 **修法(不删字)**:主干末节加「详情档 × 覆盖的原章节 × 原行段」表 + 一句「编号引用按本表定位(下沉后原编号不再有独立章节标题)」。详情档头里也写一句同样的指引。 ⛔ **不要**去改正文里的引用句(那会破坏「逐行未改」的可校验性)。 --- ## 5. 坑(都踩过) 1. **必须从备份重建**:否则第二次跑读到的是已拆过的活文件,区间校验直接 `AssertionError`。 2. **详情档编号要按文档顺序**:首跑把「浏览器验证栈」编成 `08`、而「并行调度」是 `07`(因为它先被创建)⇒ 索引表出现 `…06 / 08 / 07` 的倒序。**修法**:生成索引表时对档名 `sorted()`。 3. **行尾只信字节级**:拆分后要确认新文件是**纯 LF**(`CR=0`);文档库 `INDEX.md` 是 CR/LF 混排(CR=5/LF=270)⇒ 改它**只做字节级单行插入**,⛔ 别整文件 Write。 4. **登记跟改用「精确串替换器」**:每个 `old` 必须命中**恰好 1 次**,任一不中 ⇒ **整体不落盘**(比逐次 Edit 可靠、比 `sed` 安全,非 ASCII 不被改写)。 5. **环境**:本轮本机 `bash` 包装器**整体起不来**(`ls`/`cat` 也 `command not found`、`export PATH` 救不回)⇒ 全程改走 **PowerShell + 托管 python**:脚本先 Write 成 `.py`、输出落文件再 Read。⚠️ MSYS 路径(`/e/…`)⛔ 不许交给 Windows 原生程序(会落到 `E:\e\…` 影子目录)。 6. **对账用 Python 算 md5,别用 `md5sum`**:本机输出 `hash *path`、远端 `hash path`,多一个空格会让逐行比对假失败。 7. **按技能名匹配表格行会误命中**:用 `NAME in line` 找登记行时,**别的技能行常在描述里提到这个技能名**(实测:README 的 `dsh-architecture-lifecycle` 行写着「与 `dsh-knowledge-upkeep` 互补」⇒ 两行同时命中,登记被挂到了错行,且我按「行里含对方名」做回退时**又同时命中两行、把两行都清了**)。 **判据 = 只认行首单元格**:`l.startswith("| `08-skills//`")`,且命中数必须**恰好 1**;改动前后各打印一次「命中行号 + 行首 34 字」自证。 8. **判定远端登记文件是否「只是陈旧」——别用 SequenceMatcher 的 opcode 判据**:我要求「opcode 全为 equal/delete」时,`**` 与 ` | ` 密集的长表格行会给出**假 replace** ⇒ 误报「远端含本机没有的内容」而停手(实测:README 3 行 / INDEX 4 行全被误报,实际覆盖率 1.000)。 **正确判据 = 整行子序列覆盖率**:`sum(b.size for b in SequenceMatcher(None, 远端行, 本机行).get_matching_blocks()) / len(远端行) >= 0.98`(远端只被删减,本机只做插入)⇒ 可安全推。**更省事的 oracle = git**:`git show HEAD:` 与远端比 —— 相等即"远端 = 已提交基线,差异全是未提交改动"。 9. **`/opt/dsh/docs/` 根也镜像文档仓根**:`README.md`(技能登记表)与 `INDEX.md`(清单与状态)在服务器上各有一份 ⇒ **三处同步不能只算 `08-skills/`**。实测 2026-09-22:skills 16 文件三方 md5 全一致,而根 README/INDEX **落后一整轮登记**(差异恰为被改的 3 + 4 行)。推送后 `chmod 600` + `chown root:root`,旧副本备份到 `/opt/dsh/backups/docs-root/`。