109 lines
8.0 KiB
Markdown
109 lines
8.0 KiB
Markdown
# 技能重组 · 千行技能拆分(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 技能重组 | 技能**加载后吃多少上下文 / 判据找不找得到**(文件内部结构) |
|
|||
|
|
| `session-mechanism §10.1` | 要不要把多个技能**合并** |
|
|||
|
|
| `dsh-architecture-lifecycle` | 架构**过程文档 vs 定稿**的分层 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 通用拆分器 `scripts/split_skill.py`
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
<python> scripts/split_skill.py --spec <spec.json>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**规格格式**(照抄改):
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"skills_dir": "E:/ProgramData/.workbuddy/skills",
|
|||
|
|
"backup_dir": "E:/ProgramData/AIProject/ai1net-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/<n>/`")`,且命中数必须**恰好 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:<f>` 与远端比 —— 相等即"远端 = 已提交基线,差异全是未提交改动"。
|
|||
|
|
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/`。
|