按用户令提交:把此前未纳管的 9 个技能目录一并入库
用户令逐字:「E:/ProgramData/.workbuddy/skills 提交仓库是指的这里」—— 即本目录就是仓库(2026-10-07 已在本目录建仓,见当日日志 §22),本轮把余下未纳管的 9 个技能一并提交。 本次入库(9 个技能,46 个文件): 1、`AI HOT` 2、`draw-ui` 3、`dsh-diagnose` 4、`dsh-knowledge` 5、`dsh-local-env` 6、`dsh-opensource-release` 7、`dsh-workflow` 8、`oil-motion` 9、`skills-security-check` 提交前核对: · **凭据类扫描**(`*.env` / `*token*` / `*.key` / `*secret*` / `*.pem`)⇒ **零命中** ✓; · 体积合计约 20 MB(`draw-ui` 12M + `oil-motion` 6.5M 是大头,形态为配图/素材 —— 仓库 `.gitignore` 里明写「`assets/*.png` 是内容不是产物」⇒ 属刻意入库); · 运行产物仍按既定规则排除(`__pycache__` / `logs/` / `tmp/` / `.venv/` / `*.egg-info` / `uv.lock` / `.workbuddy/`)。
This commit is contained in:
1 parent
d26c844f64
commit
e03465c398
46 files changed
+7558
No files matched your search
@@ -0,0 +1,456 @@
|
||||
# 知识库 / 项目知识的维护与纠偏
|
||||
|
||||
> **归属**:技能 `dsh-knowledge` · 详情档(主干 `../SKILL.md`)
|
||||
> **本档覆盖**:原技能 `dsh-knowledge-upkeep` **全文**(正文 286 行 + frontmatter 变更历史)
|
||||
> **provenance**:本机版 + 文档库版的「作用域 / 宿主落点对照」块(DSH ↔ WorkBuddy 路径互译)
|
||||
> **搬运方式**:**逐行未改**;内部附件链接已改写为 `dsh-knowledge-upkeep/<附件名>`(附件在本目录下)
|
||||
> ⚠️ 原技能 `dsh-knowledge-upkeep` **已合并退役** ⇒ 见到该名按本档读。
|
||||
|
||||
---
|
||||
|
||||
|
||||
> 🔵 **作用域(2026-09-26 从 WorkBuddy 用户级技能迁入)**:本技能描述的是 **dsh 平台项目**的作业方法,已提升为 DSH 全局技能,供任何会话发现与加载。
|
||||
> ⚠️ 正文里的**宿主路径与脚本位置是迁移时的旧值**(如 `~/.workbuddy`、`CODEBUDDY.md`、`07-scripts/`、`文档库`)。~~在 DSH 上以**当前工作区的规则文件**与 `$DSH_HOME` 下的实际落点为准~~ 🔴 **2026-10-02 纠正:宿主是 WorkBuddy、不是 DSH;`$DSH_HOME` 本机已悬空** ⇒ 以**当前工作区的规则文件** + `E:\ProgramData\.workbuddy\skills\` 下的实际落点为准;⛔ 不要把正文里的旧路径当现行事实。
|
||||
> ⚠️ 冲突时:**工作区规则 > 本技能**。
|
||||
|
||||
### 🔴 宿主落点对照 —— ⚠️ **本节方向已作废(2026-10-02 纠正)**
|
||||
|
||||
> 🔴 **左右方向与现状相反,⛔ 不要再照它执行。** 下表是 **2026-09-26 从 WorkBuddy 迁往 DSH 当时**
|
||||
> 的形态(让人把 `~/.workbuddy/08-skills/<n>/` 换成 **`$DSH_HOME/skills/<n>/`**)。
|
||||
> **现状**:宿主是 **WorkBuddy**(不是 DSH);技能真身就在 `E:\ProgramData\.workbuddy\skills\<n>\`;
|
||||
> 而 `$DSH_HOME`=`E:\ProgramDSH\.dsh` 在本机**目录已不存在**(悬空老值,只剩环境变量还留着)。
|
||||
> ⇒ 正确方向是 **WorkBuddy ← DSH**:正文里见到 `$DSH_HOME/skills/<n>/`,换成
|
||||
> **`E:\ProgramData\.workbuddy\skills\<n>\`**。
|
||||
|
||||
**下表保留仅为留痕(右列=已作废的旧落点)**:
|
||||
|
||||
| 正文里的旧路径 | ~~DSH 侧实际落点~~(**已作废**) |
|
||||
|---|---|
|
||||
| `~/.workbuddy/08-skills/<n>/`、`.workbuddy/08-skills/<n>/` | ~~**`$DSH_HOME/skills/<n>/`**(技能根,被 watch,改动下一请求生效)~~ 🔴 **作废:方向反了**,现行落点=**`E:\ProgramData\.workbuddy\skills\<n>\`** |
|
||||
| `.workbuddy/memory/MEMORY.md`、`~/.workbuddy/MEMORY.md` | **`$DSH_HOME/AGENTS.md`**(全局,每轮注入)/工作区 `AGENTS.md`(项目级) |
|
||||
| `.workbuddy/memory/YYYY-MM-DD.md`(按日记忆) | **无对等物** ⇒ 教训写进本技能 `references/` 或项目台账「实测教训」节 |
|
||||
| `CODEBUDDY.md`(项目常驻规则) | **同一份文件仍生效**(已在 preset 覆盖里加入 DSH 的指令候选),无需改名 |
|
||||
| `07-scripts/<x>.py`(文档库脚本) | 同左,但**路径必须写全**:`D:\github\dsh_shenxian\dsh-server-docs\07-scripts\<x>.py` |
|
||||
| `~/.workbuddy/settings.json` 的 hooks | **`$DSH_HOME/hooks/hooks.json`** + `dsh-guard.py` |
|
||||
| WorkBuddy `automation_*` / 积分口径 | **无对等物**(见 `dsh-auto-handoff-chain` 的不可移植清单) |
|
||||
|
||||
> 🔴 **表尾收口(2026-10-02 补)**:**上表每一行的右列都属同一"迁往 DSH"的旧形态,一律作废** ——
|
||||
> 尤其第 2 行(`$DSH_HOME/AGENTS.md`)与第 6 行(`$DSH_HOME/hooks/hooks.json`):本机**没有 `$DSH_HOME`**。
|
||||
> 已实测的现行落点只有三条,照这三条找:
|
||||
> ① **技能根** = `E:\ProgramData\.workbuddy\skills\<n>\`;
|
||||
> ② **hook 注册面** = `E:\ProgramData\.workbuddy\settings.json` 的 `hooks` 段(2026-10-02 实测:本轮就是往那儿加的第 6 条);
|
||||
> ③ **记忆** = 用户级 `~/.workbuddy/MEMORY.md`(跨项目)+ 工作区 `<工作区>/.workbuddy/memory/`(按日 + 长期)。
|
||||
> ⛔ 上表左列里凡写 `$DSH_HOME/…` 的,**不要再当落点用**;`E:\ProgramDSH\` 整树已于 2026-09-28 删除
|
||||
> (见 `dsh-local-env/references/01-环境引导与迁移.md` §"同名影子树")。
|
||||
|
||||
> ⚠️ 正文其余处出现的旧路径属**历史记录**(当时的事故与实测),**保持原样不改** —— 改写历史记录等于造假。
|
||||
|
||||
---
|
||||
|
||||
# dsh-knowledge-upkeep — 知识库维护方法
|
||||
|
||||
> **一句话**:知识库不会自己变好,**只会慢慢变错**。本技能把"纠错"从临时动作变成**可复跑的方法**。
|
||||
> 素材来源:2026-09-12 全库校验实证(20 项违背 → 0)。
|
||||
|
||||
---
|
||||
|
||||
## 0. 为什么需要它(实证,不是理论)
|
||||
|
||||
| 现象 | 实测 |
|
||||
|---|---|
|
||||
| **同一事实被抄很多份** | 现行域名出现在 **21 个文件 / 123 处**;档案"下一号"写在 4 处且**互相打架**(72 / 69 / 53 / 20) |
|
||||
| **过时值长期存活** | 旧配额活在 15 个文件;实例 MemoryMax 曾同时存在两个"现役值" |
|
||||
| **权威源头本身是错的** | `BRIEF.md`(首读现状卡)自己带着旧值 → AI 老老实实读了它,读到就是错的 |
|
||||
| **校验工具查不到** | 原 `docs-audit.py` 只查**结构**(编号/悬空/重复),**不查事实** → 漂移不可见 |
|
||||
| **规模已超"全量塞入"上限** | 库 = 93 md / 555,533 字符 ≈ **388,873 tokens**;Karpathy 模式实证 **~100 篇就崩**(模型开始略读并给出自信的错误答案)|
|
||||
|
||||
---
|
||||
|
||||
## 1. 知识结构:六层 + 单一来源
|
||||
|
||||
| 层 | 内容 | 唯一权威 | 变更频率 | 送达方式 |
|
||||
|---|---|---|---|---|
|
||||
| **L0 不变量** | 架构与机制(为什么这样设计) | `01-规范/01-规划与架构` + 早期档案 | 年 | 按需 |
|
||||
| **L1 现行值** | 现在到底是什么(域名/配额/端口/路径/阈值) | **`BRIEF.md`** | 周 | 首读 |
|
||||
| **L2 规则** | 必须遵守(提问判据/红线/提交边界) | **`CODEBUDDY.md`** | 很少 | **自动注入** |
|
||||
| **L3 方法** | 怎么想、怎么做 | 3 个 dsh 技能 | 月 | 按需触发 |
|
||||
| **L4 状态** | 现在在哪(待办、落差) | `05-交接单/` + `.workbuddy/memory/MEMORY.md` | 天 | **自动注入** |
|
||||
| **L5 历史** | 怎么变成现在这样 | `04-调整方案/` + `09-archive/` | **只增不改** | 按需 |
|
||||
|
||||
### 1.1 L0.5 架构定稿(2026-09-21 补)
|
||||
|
||||
⚠️ **L0 与 L1 之间还有一层**,原表漏了,导致**架构级结论无处安放** ⇒ 只能塞进 L5 过程档案 ⇒
|
||||
「后续会话看不全、同一架构多版口径」。
|
||||
|
||||
| 层 | 内容 | 唯一权威 | 变更频率 |
|
||||
|---|---|---|---|
|
||||
| **L0.5 架构定稿** | 架构级的「**现在该做成什么样**」(成品,可直接据此施工) | **`02-架构设计/`** | **中**(架构本身变才改) |
|
||||
|
||||
- **L0(为什么这样设计)** vs **L0.5(该做成什么样)**:前者是**原理**,后者是**目标形态**;
|
||||
L0.5 比 L0 变得勤,比 L1(现值)稳。
|
||||
- **L0.5 与 L5 的分工**:L5 记录「当时怎么想的」(**会过时、⛔ 不回改**),
|
||||
L0.5 声明「现在该做成什么样」(**不轻易变、冲突以它为准**)。
|
||||
- 🔴 **完整机制(目录契约 / 命名 / 收敛五步 / 状态块纪律)⇒ 技能 `dsh-architecture-lifecycle`**,
|
||||
本表只登记层位,⛔ 不在此重复规则。
|
||||
|
||||
**三条铁律**
|
||||
1. **每个事实只有一个权威** —— 其余文件**只许写指针,不许复制数字**。
|
||||
2. **L5 冻结** —— 历史档案里的旧值**不回改**(写的时候是对的,回改破坏历史);需要修正时**在文末追加「修正(YYYY-MM-DD)」小节**,并改 L1 的现值。
|
||||
3. **校验必须能查"事实一致性"** —— 否则前两条必然失守。
|
||||
|
||||
---
|
||||
|
||||
## 2. 分层判据:**什么必须实体,什么可以只给指针**
|
||||
|
||||
> **"不做会违规、会出事"的 → 实体保留;"看了更准但不看也不违规"的 → 才给指针。**
|
||||
|
||||
| 处理 | 内容 |
|
||||
|---|---|
|
||||
| **实体保留**(压缩不许删语义) | 提问判据 · 红线 R1–R8 · 提交边界 · 规划/执行分离 · 并发纪律 · **会导致事故的实测事实** · 环境要点 |
|
||||
| **只给指针** | 平台现状与历史细节 · UI 规范全文 · 档案模板细则 · 某功能的实现内幕 |
|
||||
|
||||
⚠️ **指针必须绑定可识别的动作**,否则"去查"不会发生:
|
||||
`当你要写前端页面 → 先读 06-UI规范`,而不是 `详见 06-UI规范`。
|
||||
|
||||
---
|
||||
|
||||
## 3. Lint 四件套(可复跑,退出码可接 CI)
|
||||
|
||||
```bash
|
||||
python3 07-scripts/docs-audit.py # 结构:编号冲突 / 标题号不符 / 悬空引用
|
||||
python3 07-scripts/docs-manifest.py # 刷新机读清单 docs-manifest.json(含被引次数/tier)
|
||||
bash 07-scripts/docs-sync-check.sh # 双端对账(本机 ↔ /opt/dsh/docs)
|
||||
python3 07-scripts/docs-consistency.py # 事实:写死取值 + 跨页取值冲突
|
||||
```
|
||||
|
||||
**`docs-consistency.py` 的设计要点**(写它时踩过的坑,别重踩):
|
||||
- 只查**高置信**模式。首版把「旧域名」「旧配额」当违规 → **几乎全是误报**(库里都是"旧域已 301" / "512M→384M" 的合法表述)。
|
||||
- **引号感知**:引号内的匹配 = **引用历史**,不是断言,不算违规。
|
||||
- 判据 = 「**承诺现行**的文件不许含已废止/写死/矛盾的取值」;
|
||||
**历史豁免**:`04-调整方案/**`、`09-archive/**`、`01-规范/01-规划与架构`、`01-规范/02-运维手册`。
|
||||
|
||||
**建议补跑(不在四件套内,但会毁可读性)—— 编码合法性**
|
||||
```python
|
||||
for p in all_md: # 用 open(p,'rb').read().decode('utf-8') 整块判,别逐字节
|
||||
try: open(p,'rb').read().decode('utf-8')
|
||||
except UnicodeDecodeError as e: print(p, e.start)
|
||||
```
|
||||
🔴 **只有几个非法字节也会毁掉整份文件**:自动编码检测判"不是 UTF-8"⇒ 走偏为 GBK ⇒ **整份读出来全乱码**。
|
||||
(2026-09-16 实测:212 KB 的日志因**开头 2 字节**坏 ⇒ `Read` 全文乱码。处置见 `PLAYBOOK §10.1`。)
|
||||
|
||||
---
|
||||
|
||||
### 3.1 交接单的「口径指纹」—— 改正文必连带(2026-09-16 实测)
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 算法 | `tail -n +4 <单子>.md \| md5sum`(**跳过前 3 行**:标题 / 空行 / 指纹行) |
|
||||
| 含义 | 前 3 行**不参与** ⇒ 改指纹行本身不破指纹;改**正文任何一字** ⇒ 指纹必变 |
|
||||
| 引用点 | ① 单子第 3 行 ② 接续包/入口里对它的引用 ③ 记忆(`MEMORY.md` / 日志)里记的值 |
|
||||
|
||||
🔴 **改完单子正文的正确收尾顺序**:改正文 → **复算指纹** → 写回第 3 行 → 用脚本**全文 `replace`** 所有引用点(⛔ 别手改)→ 复算自证「实算值 == 三处登记值」。
|
||||
⚠️ **一次要改多处正文时,先全改完再算一次**(实测改两轮 ⇒ 指纹算三轮 ⇒ 白绕 2 轮工具调用)。
|
||||
|
||||
**⛔ 最容易漏的不是单子,而是"单子之外"的过期口径**(本轮真漏过一次):
|
||||
```bash
|
||||
grep -rn '<旧口径关键词>' --include='*.md' . # 例:未修 / 待专门一轮 / 已定位、未修
|
||||
```
|
||||
- **文件顶部 `>` 摘要块最容易残留** —— 下一棒第一眼读的就是它。
|
||||
- 单子的**历史章节**(如"§11.6 当时未修")**保留原文 + 加勘误段/标题后缀**,别删 —— 历史可追溯,且不会误导跳读者。
|
||||
|
||||
---
|
||||
|
||||
## 4. 漂移处理 SOP(发现 → 收敛,五步)
|
||||
|
||||
```
|
||||
① 发现 → 四件套任一项退出码非 0
|
||||
② 定性 → 真漂移 / 合法历史表述 / 校验器误报 ← **这一步不能跳**
|
||||
③ 定权威 → 这个事实的**唯一权威**是哪一层哪个文件(见 §1)
|
||||
④ 收敛 → 改权威文件;其余副本改为指针或删除;写死值改为"复跑取号"
|
||||
⑤ 复跑+同步 → 四件套全绿 → scp → 复跑 docs-sync-check.sh
|
||||
```
|
||||
|
||||
**② 定性是分水岭**——2026-09-12 首跑报 20 项,逐条看上下文后:
|
||||
- 真漂移 **≈6 项**(下一号三处矛盾、MemoryMax 两处取值不一致、`maidou`、旧路径、SSH 端口)
|
||||
- **合法历史 14 项**("旧域已 301" / "512M→384M" 的迁移表述)
|
||||
⇒ 若不做定性就批量改,会**破坏历史档案**并制造新错误。
|
||||
|
||||
---
|
||||
|
||||
## 5. 自动化的边界(**重要**)
|
||||
|
||||
| 动作 | 可否自动 | 理由 |
|
||||
|---|---|---|
|
||||
| **检测**(跑四件套、出报告) | ✅ **可以,且应该** | 只读、零风险、退出码可判定 |
|
||||
| **刷新派生件**(`docs-manifest.json`) | ✅ 可以 | 纯派生,无人工语义 |
|
||||
| **改写正文 / 批量替换** | ❌ **不可以** | ① 触 **R7**(批量写入须确认)② **认识论漂移**:LLM 改知识库后,错误会成为后续输入并**复利放大**(LLM Wiki 社区已实证)③ 研究员明确指出:**git diff + 人工审阅才是真正的安全网** |
|
||||
| **删历史档案里的旧值** | ❌ 不可以 | 违反铁律 2(L5 冻结) |
|
||||
|
||||
⇒ **推荐形态**:自动任务**只做"体检 + 出报告"**,**发现违背时通知人**,由人或新会话按 §4 SOP 收敛。
|
||||
|
||||
---
|
||||
|
||||
## 6. 反例(今天真实踩过,别重犯)
|
||||
|
||||
| # | 反例 | 后果 | 规避 |
|
||||
|---|---|---|---|
|
||||
| 1 | 校验规则太宽(拿"旧域名"当违规) | 20 项里 14 项误报 → 校验器被忽视 | 只留高置信模式;拿不准就不查,改人工 |
|
||||
| 2 | 改了**技能工作副本**却忘了**归档副本** | 校验器扫的是归档副本 → 改了等于没改 | 技能两处位置必须同步(md5 一致) |
|
||||
| 3 | 备份文件 `*.bak-*` 留在**文档库内** | 污染 `docs-sync-check`(算成"仅本地") | 备份放库外,或收尾删掉 |
|
||||
| 4 | scp 时把 `07-scripts/x.py` 也推到**根目录** | 服务器多一份同内容副本 → 对账报"仅服务器" | scp 目标路径逐个核对 |
|
||||
| 5 | 把"下一号"写死在入口文档 | 并行改动 3 次打穿(83→85→87) | 一律"复跑取号" |
|
||||
| 6 | **只看根目录就说"本机没有这个文件"** | 实际在 `07-scripts/` 下 → 结论完全相反(今天两次) | 报"不存在/缺失"前先 `find` 全库 |
|
||||
| 7 | **把问题抛给用户前没确认它还在** | 服务器多余文件已被并行会话清掉 → 问了个**已消失**的问题 | 抛出前重新查一次;状态会被别人改变 |
|
||||
| 8 | 没顺着 grep 找权威档案就猜文件用途 | 直接读关联档案的 TL;DR 一句话就清楚,比猜快得多 | `grep -rn <文件名> --include="*.md"` → 读那条档案的头部 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 自检清单(每次动知识库前后)
|
||||
|
||||
**动手前**
|
||||
1. 这个事实的**唯一权威**在哪一层?我是不是正准备在别处复制它?
|
||||
2. 我要改的是**承诺现行**还是**历史**文件?历史的 → 不回改,追加"修正"小节。
|
||||
3. 涉及 >10 文件?→ 先出清单(R7)。
|
||||
|
||||
**收尾**
|
||||
4. 四件套**全绿**了吗?
|
||||
5. 两处技能副本 md5 一致吗?推服务器了吗?
|
||||
6. 临时/备份产物清了吗?
|
||||
7. 复数入口都改成"复跑取号"了吗?
|
||||
|
||||
---
|
||||
|
||||
## 8. 文档的「无效信息」分类与高价值写法(2026-09-16 加)
|
||||
|
||||
> **起因**:用户问「AI 会话生成的文档是否有无效信息?是否需要一套方法,写出**简单明了、高价值、且不影响模型阅读**的文档?」
|
||||
> §4 处理的是「漂移」**一类**;本节把它扩成 **六类无效信息** + **不能删的红线** + **写作形态**。
|
||||
|
||||
### 8.1 唯一判据(正反两面)
|
||||
|
||||
> **去掉这一行,下一个会话会不会「做错事」或「变慢」?**
|
||||
> **会 ⇒ 必须留**(还要让它落在首屏);**不会 ⇒ 可删 / 可降级到归档。**
|
||||
|
||||
正面用法(决定"值不值得写"):**这行会改变读者的下一步动作吗?** 不会 ⇒ 它只是背景装饰,压缩或删。
|
||||
|
||||
⛔ **别把"更简洁"当目标** —— 目标是**行为相关性**。把必要的「为什么」删掉,AI 会在同一处**反复摇摆**,那是负收益。
|
||||
|
||||
### 8.2 六类无效信息(2026-09-16 全部亲见,不是理论)
|
||||
|
||||
| # | 类型 | 当日实例 | 处置 |
|
||||
|---|---|---|---|
|
||||
| 1 | **与可执行体不符** | `stop-dialog-guard.py` 注释写「15 万/120」,代码是 `120000/80` | ⛔ 以**代码 / 实测**为准改注释,不是反过来 |
|
||||
| 2 | **过期结论仍占"生效位"** | 记忆里「hook 确实在生效(09-13 取证)」——当天已被推翻 | **不删**:标注「已推翻 + 新结论 + 日期」,保留纠偏轨迹 |
|
||||
| 3 | **同一事实多处重复** | 同一技能在本机 / 文档库 / 镜像三副本 | 收敛到**单一来源**,其余只留指针(§2) |
|
||||
| 4 | **过程流水挤掉结论** | 日志里「我做了什么」淹没了「现在是什么状态」 | **结论前置**;流水降级到日志 / 附录 |
|
||||
| 5 | **中间产物混进正式文档** | 项目根 `tmp/`(旧 `_tmp_*` 已归位)、`待清理/`(原中间产物区) | 集中到归档区,永不进正式文档;收尾清 |
|
||||
| 6 | **只写"给人看的套话"** | 「这个很重要」「要注意」—— 不含任何判据 | 换成**可执行判据**:什么条件下、做什么动作 |
|
||||
|
||||
### 8.3 ⛔ 不能删的红线(「不影响模型阅读」的边界)
|
||||
|
||||
**删「结论的装饰」,留「判断的依据」。** 以下五类删了会直接坏事:
|
||||
|
||||
1. **判据与阈值**(数字 / 边界条件 / 优先级)
|
||||
2. **命令原文与路径** —— 删了 AI 得重新试错,这是**最贵的成本**
|
||||
3. **反例与踩坑**(现象 → 根因)
|
||||
4. **「为什么」(决策依据)** —— 删了会在同一处反复摇摆
|
||||
5. **失效 / 作废标注** —— 删了会让旧做法「复活」
|
||||
|
||||
### 8.4 高价值文档的形态(动笔前先定这五条)
|
||||
|
||||
1. **结论先行** —— 首屏 3 行给判定
|
||||
2. **状态与流水分离** —— 状态(现在是什么)= 常驻;流水(怎么变的)= 追加 ⇒ **分文件放**
|
||||
3. **一事实一处** —— 其余给指针
|
||||
4. **可执行** —— 给命令 / 判据 / 路径;⛔ 不给"建议注意"
|
||||
5. **排版** —— 按 `session-mechanism §5.4`(每节 ≤7 行 / 表格 ≤5 列 / **并列项各占一段**)
|
||||
|
||||
### 8.5 自检三问(贴出去之前过一遍)
|
||||
|
||||
1. **读者是「下一个会话」**(不是人)—— 它读完能**直接动手**吗?
|
||||
2. 这份里**有多少行会改变下一步动作**?占比低 ⇒ 该压缩。
|
||||
3. 我删掉的每一行,**有没有落在 §8.3 的红线**里?
|
||||
|
||||
### 8.6 「注入预算」维度 —— 长文件的后半段等于不存在(2026-09-16 加,实测)
|
||||
|
||||
> §8.1–§8.5 用「**行为相关性**」判该不该留。另有一条**独立于内容质量**的约束:
|
||||
> **每轮注入是有上限的** —— 超出上限的部分宿主不会给模型,**效果上等于这段不存在**。
|
||||
|
||||
- **实测(2026-09-16)**:用户级 `MEMORY.md` 20,977 B / 11,712 chars,宿主**注入上限 ≈ 4,000 chars**。原排序下窗口只覆盖「钩子配置 + 环境路径」,**整节 `Preferences`(全部行为规则)落在窗口外** ⇒ 规则没写错,是**排序错**。
|
||||
- **⇒ 判据升级**:一份「每轮都要生效」的文件要同时过 **两条** —— ① 每行都过 §8.1 的判据;② **体量 ≤ 注入上限**(或硬规则必须全部落在窗口内)。超限时**先重排、后删减**:重排零损失,删减有丢规则风险。
|
||||
- **⛔ 不要用「删内容」解决超限**,正确顺序是:
|
||||
1. **分类** —— 哪些是「每轮必须生效」(硬规则 / 事故级事实 / 禁令),哪些是「查阅型」(历史细节 / 取证过程 / 个别项目偏好)
|
||||
2. **把硬规则整体排到最前**,使注入窗口正好覆盖它们
|
||||
3. 查阅型内容留在窗口外**不算丢失**(本机仍可读),只需在其上方留一行指针
|
||||
4. 重排仍不够才压措辞 —— 且**受压的必须是查阅型,不得压判据与命令原文**(§8.3)
|
||||
- **📌 排序契约(防复发)**:文件头必须显式写明「**注入上限 ≈ N 字符;排序即重要性;新增内容按序插入对应小节,⛔ 不要追加到末尾**」。少了这一行,下一次追加就会把重要规则顶出窗口 —— 这是**慢性失效**,没有任何报错。
|
||||
- **适用面**:一切「每轮注入」的文件 —— `MEMORY.md`、`CODEBUDDY.md`、`.codebuddy/rules/*.md`、各技能的 `description`。
|
||||
- **可复跑自检**:`wc -c <file>` ÷ ~1.8 ≈ 字符数,与上限比;**更直接的判据 = 看注入块结尾有没有被截断**(结尾被截断 ⇒ 已有内容在静默失效)。
|
||||
|
||||
## 9. 技能集可用性体检(**10 层取证法**,2026-09-22 立 · 可复跑)
|
||||
|
||||
> **什么时候用**:技能越攒越多后,怀疑「会话还能不能准确挑对技能」「这么多技能好不好维护」时。
|
||||
> **一句话判据**:技能是**按 `description` 匹配、按需加载**的 ⇒ 体检的对象是 **description 的区分度**,不是技能正文写得好不好。
|
||||
|
||||
### 9.1 十层(每层一个脚本,落盘跑,⛔ 别用 `python -c`)
|
||||
|
||||
| 层 | 查什么 | 关键判据 |
|
||||
|---|---|---|
|
||||
| 1 | description 全量 + 探针词重叠 | ⚠️ **探针词太宽必假阳性** —— "文档""规则""会话"这类泛词必然命中一堆技能 |
|
||||
| 2 | **精确提取引号内触发短语** | **零重复 + 零包含** = 设计层区分度健康 |
|
||||
| 3 | 真实说法 → 命中谁 | 期望技能**不在**命中集 = ❌ 真缺陷(比"撞"严重) |
|
||||
| 4 | 三处一致性 / 引用 / 版本 / 体量 | 三处 md5 必须**零差异** |
|
||||
| 5 | 引用上下文逐条看 | 报出的"悬空引用"**必须逐条看原文语境** —— 插件名 / 工作区名 / npm 包名全是假阳性 |
|
||||
| 6 | 自然说法覆盖率 | 盲区 = 用户会这么说但 description 没写 |
|
||||
| 7 | **用历史会话原话做验证集** | 最强的一层,见 §9.3 |
|
||||
| 8 | 盲区二分 + 补漏词重跑 | 追问型(<8字 / 含指代且<40字)**属正常**;只查独立型 |
|
||||
| 9 | **用户明确定过的规则**是否接得住 | 见 §9.2 —— 最容易漏、后果最重 |
|
||||
| 10 | frontmatter 结构校验 | 字段**唯一性**(重复字段会让解析取到哪个不确定)+ 版本 x.y.z |
|
||||
|
||||
### 9.2 🔴 最高价值的一层:规则写下来了,但没技能接得住
|
||||
|
||||
**做法**:把用户**明确定过、且已写进规则文件**的硬要求逐条列出,用**各技能 description 原文**做字面检查 ⇒ 谁都不含 = **真盲区**。
|
||||
|
||||
**实测(2026-09-22)**:12 个技能里,**7 条规则没有任何 description 会命中**,其中:
|
||||
- 「**只做被明确要求的事**」—— 该规则**已写在 `session-mechanism §5.3` 正文里**,但历史原话里出现 **62 次**、12 个 description 一个都没命中;
|
||||
- 「先抢锁再动手」「技术讨论不谈法规」「要的是解决问题不是将就妥协」「不要放 D 盘」「文档直入主题」「点名主体别用代词」同类。
|
||||
|
||||
⇒ **这是 `skill-load-guard.py` 同族问题的另一种形态**:那个治"用户点名了方法但技能没被加载",这个治"用户点名了规则但技能没被匹配"。**规则写在文件里 ≠ 会在正确的时机被取用。**
|
||||
|
||||
**修法**:给承载该规则的技能**补 description 触发词**(用用户的原话措辞,⛔ 不要用书面语),并写明这是高频场景。
|
||||
|
||||
### 9.3 ⚠️ 判据漏词会**伪装成技能盲区**(最容易自我误导)
|
||||
|
||||
**实测**:第 7 层第一次跑出「命中 0 = 55.8%」,看着像大面积失效;改进关键词表后降到 **42.9%**。
|
||||
⇒ 差值全是**我的判据漏词**,不是技能缺陷。
|
||||
|
||||
**铁律**:报"盲区"之前,**必须先用目标技能的 description 原文核验一遍**(`grep` 那几行),确认不是我自己的关键词表不全。
|
||||
⛔ **不许把"我判据里补的词"当成"技能已覆盖"** —— 这两者完全不同(本次差点犯:我在分析判据里给 `session-mechanism`(内 `references/作业规矩/`) 补了"成本/token",而它 description 里根本没有这些词,是真盲区)。
|
||||
|
||||
### 9.4 撞点怎么判:**不是所有撞都要消**
|
||||
|
||||
- ❌ **恶性撞**:撞了但**偏向错误的一方**(该加载的没加载)⇒ 必须修。
|
||||
修法 = ① 补上缺的说法 ② 给两边都加**「⚠️ 与 `对方` 的边界」**说明(**双向指认**)⇒ 会话加载任一个都能看到分诊规则。
|
||||
⚠️ 实测:仅补词会让撞点"数量变多"(原来只命中错的一方,现在两边都命中)—— 但**性质变好**了,别被计数误导。
|
||||
- ✅ **良性撞**:多个技能**分工互补**(判类型 / 怎么定案 / 怎么落地),多加载一个是好事。**不要为了"看起来干净"去削它们。**
|
||||
|
||||
### 9.5 两条自己踩过的坑
|
||||
|
||||
- 🔴 **改 frontmatter 后必须数一遍字段出现次数**:本次改 `description` 时 `old_string` 少截一行 ⇒ **产生了两个 `last_change`**。⚠️ 无人守这个唯一性 ⇒ 改完逐字段清点(或用 §9.1 第 10 层脚本)。
|
||||
- 🔴 **`python -c` 里带反引号会被 shell 吃掉**(本次改 README 时 pattern 匹配 0 行、**静默无效果**)⇒ 一律**落盘 `.py`** 再跑。
|
||||
|
||||
## 10. 技能重组:千行技能拆分(主干 + 详情档,2026-09-22 立 · **可复跑**)
|
||||
|
||||
> **与 §9 的关系**:§9 = **体检**(发现技能集的问题)|§10 = **整改**(把过长技能拆开)。两者是同一职责的两端。
|
||||
> **触发**:某技能 `SKILL.md` 已到千行级(≈≥800 行)。**判据**:它长不是因为内容多,而是因为**判据与详情混在一起** —— 每次加载都吃掉大量上下文,而其中大半是长表、实测记录、历史细节。
|
||||
|
||||
**目标形态**:`SKILL.md` = 判据 + 流程主干 + 命令骨架;长表 / 案例 / 实测记录 / 历史细节 ⇒ `references/<两位序号>-<主题>.md`。**原位只留一行指针**(点名该档覆盖的章节),每档带头:`归属 / 本档覆盖 / 主文件指针 / 原行段`。
|
||||
|
||||
**四条硬约束**(用户要求「功能效果不受影响」):
|
||||
|
||||
1. **内容守恒** —— 拆分前后总行数不变(可机械校验);⛔ **不许借机删任何判据 / 命令 / 事故事实**。
|
||||
2. **判据实体常驻** —— 按 §2 分层判据:「不看会导致违规/事故的」**必须留在主干**,其余才可下沉。
|
||||
🔴 **口径:判据常驻 > 行数目标**。判据密集的技能,`≤350 行`可能**结构上不可达**(实测:`dsh-opensource-release` 的硬规则实体 R-O1–R-O17 + 事故清单 + 事实 + 授权 + 验证八件套 ≈ **453 行**)⇒ 达不到就**如实报告并给出数字**,⛔ 不许为凑行数把硬规则降级成指针。
|
||||
3. **每档自包含** —— 头里写明「本档覆盖哪些原章节 + 原行段」,让跨档引用可定位。
|
||||
4. **三处同步 + 登记跟改** —— 本机 `.workbuddy/08-skills/<n>/` → 文档库 `08-skills/<n>/` → `/opt/dsh/docs/08-skills/<n>/`(**600 root:root**);`README.md` / `INDEX.md` 加「详情已拆到 `references/`」。
|
||||
🔴 **登记两处易漏**:① **服务器上也有根登记副本**(`/opt/dsh/docs/README.md` · `/opt/dsh/docs/INDEX.md`,600 root:root)⇒ 三处同步**要连它们一起推**,别只推 `08-skills/`(实测:skills 16 文件三方一致,根登记却落后一整轮);② 表格行**按行首单元格匹配**(`l.startswith("| `08-skills/<n>/`")`)—— ⛔ 别用「技能名出现在这一行里」判命中(别的技能行常在描述里提到它 ⇒ 会把登记挂到错行)。旧副本先备份到 `/opt/dsh/backups/docs-skills/`(根登记 → `/opt/dsh/backups/docs-root/`)。
|
||||
|
||||
**执行四步(纯机械搬运,逐行未改)**:
|
||||
|
||||
1. **先备份 = 回滚点**:`tmp/_keep-<日期>/split-bak/<技能>-SKILL.md`。
|
||||
2. **取行号**:列出全部 `^#{1,3} ` 标题 + 行号 ⇒ **只按整节/整段**划搬运区间,区间⛔ 不得重叠。
|
||||
3. **跑 `07-scripts/split_skill.py`**(**声明式规格 ⇒ 纯搬运**;脚本自校验「行数守恒 / 逐行包含 0 丢失 / 节可寻址 / frontmatter 唯一」,并把结果 print 成摘要)。**可重复跑**:永远从备份重建 ⇒ 不会二次切割。
|
||||
4. **同步 + 三方对账**:本机 / 文档库 / 服务器 **逐文件三方 md5 比对**,`不一致 0` 才算完成。
|
||||
|
||||
**四项验收(缺一不可)**:① 守恒(主干内容 + 详情行数 = 原行数)② 三处 md5 一致 ③ 原**每一个标题**在新结构里可寻址 ④ frontmatter 唯一、`version` 不变。
|
||||
|
||||
**🔴 下沉必带的副作用 —— 跨档引用会断头**:正文里的 `见 §8 坑 9` / `见坑 36` / `见下表:X` 这类编号引用,目标一进详情档就没人接得住(实测一次抓到 **5 处**)。**修法** = 主干末节加一张「详情档 × 覆盖的原章节 × 原行段」表,编号按这张表定位。
|
||||
**执行清单与实测数据 ⇒ `references/10-技能重组-千行拆分.md`**;通用拆分器 ⇒ `07-scripts/split_skill.py`(`--spec <json>`)。
|
||||
|
||||
## 实测坑(2026-09-20)· 新增目录的文档命名 vs docs-audit 编号
|
||||
|
||||
- `docs-audit.py` 的编号正则是 **`^(\d+[a-z]?)-`(只看文件名,不看所在目录)** ⇒ 在**新目录**里用 `01-/02-/03-` 命名会与 `04-调整方案/01..03` **撞号**,结论变 **RC=1**(【1】档案编号冲突)。
|
||||
- ⇒ 新增目录的文档一律用**非数字前缀**(如 `DB-00-…`);**标题里可继续保留序号** —— 检查【2】只在"文件名与标题**都**解析出编号"时才比较。
|
||||
- 附带两条(同日同批踩到):**跨下划线的标识符替换不要用 `\b`**(`\bV12\b` 匹配不到 `SQLITE_V12`,`_` 是 word 字符);**改共享 md(`INDEX.md` / `05-交接单/README.md` 等)必须字节级单行插入** —— 该库存在 CR/LF 混排文件(`INDEX.md` CR=5/LF=270),整文件 Write 会把行尾**静默改写**。
|
||||
- 收口三件套仍是:`docs-audit.py` **RC=0** + 新档**纯 LF**(`CR=0`)+ 改共享文件后核对 `CR` 计数**未变**。
|
||||
|
||||
## 11. 归档镜像结构治理与双端对账(2026-09-24 立 · 可复跑)
|
||||
|
||||
**适用**:文档库目录改名 / 结构重排后,让服务器归档镜像(`/opt/dsh/docs/`)与本机镜像对齐,并让 `docs-sync-check.sh` 真正归零。
|
||||
|
||||
### 11.1 先做三分表,不要凭「路径不在」下判断
|
||||
|
||||
判定服务器独有文件前,先把服务器路径按改名规则**映射**成规范路径,再按两维交叉分类:
|
||||
|
||||
| 映射后路径在库 | 内容(归一化 md5)在库 | 归类 | 动作 |
|
||||
|---|---|---|---|
|
||||
| 是 | 是 | 已一致 | 不动 |
|
||||
| 是 | 否 | **服务器旧版** | 用库版本覆盖 |
|
||||
| 否 | 是 | 改名 / 搬路径 | 迁到新路径 |
|
||||
| 否 | 否 | **真独有** | 归档保留,⛔ 不删 |
|
||||
|
||||
⚠️ 只按「路径不在库」列「服务器独有」会把**改名**误判成**独有**(实测:路径级报 121 个独有,内容级降到 22 个,其中 17 个是真正的历史旧档)。
|
||||
|
||||
### 11.2 判「旧名残留」必须带负向后顾
|
||||
|
||||
`grep "调整方案/"` 会把**正确的新前缀** `04-调整方案/` 一并命中 ⇒ 假阳性(实测:裸 grep 报 14 个文件,精确正则报 **0**)。正确写法两条并列:
|
||||
|
||||
```python
|
||||
re.compile(r'(?<![0-9A-Za-z_\-])调整方案/') # 库内相对路径
|
||||
re.compile(r'/opt/dsh/docs/调整方案/') # 服务器绝对路径(前导 / 需单列一条)
|
||||
```
|
||||
|
||||
⚠️ 负向后顾里**含 `/` 会漏掉** `/opt/dsh/docs/旧名/` 形态 ⇒ 绝对路径必须单列。同类的假阳性还有:relay 日志里的 `host=ops/w-106`、git 仓库路径 `HEAD:scripts/x.cjs`、散文里的「交接单 / 接续包」、`~/.workbuddy/skills/`、`/api/skills/{shared,mine}`、`src/host/skills/plugin.ts` —— 这六类**不是**文档库段名。
|
||||
|
||||
### 11.3 判「两端一致」必须忽略行尾
|
||||
|
||||
Windows `\r\n` vs Linux `\n` 会让 `diff` **整块报差异**,但归一化(`\r\n→\n`)后 md5 **逐行相同**。只信值比较,⛔ 不信 `diff` 退出码。
|
||||
|
||||
### 11.4 改目录名后的**必查清单**(本线实测漏过其中两项)
|
||||
|
||||
1. **功能性常量**:锁脚本 `LOCKDIR / LOCKEXEC / LOCKSROOT`、宿主 hook 的 `paths` 与命令、`.gitignore`。
|
||||
2. **自检脚本的前缀常量**:`docs-manifest.py`(档案号前缀)· `docs-audit.py`(编号扫描范围)· `docs-consistency.py` · `docs-archive-index.py` · `docs-search.py`。⚠️ **rc=0 也可能是「扫到了空目录」** —— 前缀写旧名时判定恒空 = **静默假绿**(实测:manifest 输出「0 篇档案」还判「与 INDEX 一致」)。
|
||||
3. **`.codebuddy/rules/*.md` 的 `paths:` glob**(条件规则不匹配 ⇒ 静默不生效)。
|
||||
4. **生成物需重建**:`docs-manifest.json` 是**派生数据**(含 `counts.chars`),任何 md 改动后必须重建并**双推**,否则对账恒报「内容不一致」(实测差异 83 字符 = 修复造成的字符增量)。
|
||||
5. **对账脚本的排除项**:运行态(`tmp/`、`05-交接单/.locks/`、`.doing-*`、`.exec-lock*`)必须排除,否则永远「仅本地 N 个」。
|
||||
6. **技能文档的自指约定**:技能正文写死的服务器归档位(如 `/opt/dsh/docs/08-skills/<name>/SKILL.md`)。
|
||||
7. 🔴 **工作区「搬家 / 收口」时的脚本内绝对路径常量** —— `diff -rq` 只比**内容**,**查不出「脚本里写着旧工作区绝对路径」这类路径级引用**(2026-09-27 实测:两份工作区副本内容 0 差异,但两个自测脚本的 `DEVKIT` 常量仍指向旧副本 ⇒ 在新副本跑 = **假验证**,实际执行的是别人的脚本)。两步做法:① `grep -rn "<旧工作区绝对路径>"` —— ⚠️ **必须显式扫隐藏目录**(ripgrep 默认跳过 `.workbuddy/` 这类,漏扫则整类缺陷不可见);② 修成 **`${VAR:-<新路径>}` 兜底形态**,让下次搬家用环境变量覆盖即可,⛔ 不必再逐个改脚本。
|
||||
|
||||
### 11.5 服务器侧改造的安全姿势
|
||||
|
||||
- **动手前**:`tar czf /opt/dsh/backups/docs-pre-restructure-<ts>.tar.gz -C /opt/dsh docs`(全量快照)。
|
||||
- **扩量推进**:先把新结构解包就位(只增),再把旧结构 **`mv` 到 `/opt/dsh/backups/docs-old-structure-<ts>/`** —— ⛔ 不用 `rm` ⇒ 可回滚、零信息丢失(真独有文件随之留档)。
|
||||
- **改名后必须复查运行时依赖**:`systemd` / `/etc/dshs.env` / `crontab` / `bashrc` / 平台代码(`/opt/dshs/src` 等)/ 实例 profile —— 全 grep 一遍旧路径。实测本平台**零引用**(`/opt/dsh/docs` 是纯归档位),但这条不能省:结论要靠取证,不靠「应该是归档位」。
|
||||
- **权限**:目录 `700` / 脚本 `755` / 其余 `600`,`root:root`。
|
||||
|
||||
### 11.6 验收判据
|
||||
|
||||
`docs-sync-check.sh` 报 **「双端一致 ✅」**(一致 N / 内容不一致 0 / 仅本地 0 / 仅服务器 0)+ 库内五件套 rc=0(`docs-consistency` / `docs-archive-index` / `handoff-status` / `docs-audit` / `docs-manifest`)。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 变更历史(原 frontmatter · 逐字保留)
|
||||
|
||||
```text
|
||||
name: dsh-knowledge-upkeep
|
||||
description: dsh 平台文档库 / 项目知识的**维护与纠偏方法**。当发现「文档与现状不符」「同一事实多处打架」「知识越积越碎」「AI 忘了某条规则」「要收敛或重构知识库结构」时使用;也用于定期体检。**技能集专项**:说「**这么多技能会不会话能准确调用 / 挑不对技能 / 技能太多不好维护**」时 ⇒ **直接看 §9「技能集可用性体检(10 层取证法)」**。核心 = 六层知识结构(L0-L5,**L0.5 架构定稿见 §1.1**)+ **分层判据(实体 vs 指针)** + Lint 四件套 + **漂移处理 SOP** + **自动化的边界(检测可自动,改写不可自动)** + 今天踩过的 5 个反例 + **§8.6「注入预算」维度**(每轮注入有上限 ⇒ 长文件后半段等于不存在;**先重排、后删减**)+ **§9 技能集体检 10 层(含"规则写下来了但没技能接得住"这一最易漏层)**。**配套:`dsh-architecture-lifecycle`(架构定稿层与收敛流程)· `session-mechanism`(谁定什么 / 怎么定得对,内 `references/02-功能优先协作协议.md`)· `dsh-change-workflow`(怎么落地)。**
|
||||
version: 1.0.0
|
||||
updated_at: 2026-09-24
|
||||
last_change: 【2026-09-24 新增 §11「归档镜像结构治理与双端对账」—— 本线完成服务器 `/opt/dsh/docs` 归档镜像的结构改造(A 方案)后沉淀:三分表判定法(映射后路径 × 内容交叉分类)· 负向后顾正则(裸 grep 会把新前缀 `04-调整方案/` 命中 ⇒ 假阳性)· 两端比对忽略行尾 · 改名后必查六项(功能性常量 / 自检脚本前缀 / 条件规则 paths / 生成物重建 / 对账排除项 / 技能自指)· 改造安全姿势(全量 tar + 旧结构 mv 归档,⛔ 不 rm)。】先前 2026-09-22 按要求统一版本号】frontmatter `version` → `1.0.0`(原 v1.4.0);正文与历史中的版本号为当时记录,未改动。此前 v1.4.0(2026-09-22):新增 **§9「技能集可用性体检(10 层取证法)」** —— 起因:用户问「这么多 dsh 技能会不会话能准确使用 + 后续方便维护」,用 10 层递进取证回答。要点:① 体检对象是 **description 的区分度**(技能按需匹配加载);② **§9.2 最高价值层 = 查"用户明确定过的规则有没有技能接得住"** —— 实测 12 个技能里 7 条规则无任何 description 命中(「只做被明确要求的事」历史原话出现 62 次且已写在本技能正文里,却接不住);③ **§9.3 判据漏词会伪装成技能盲区**(55.8% → 42.9% 全是我的关键词表不全,⛔ 不许把"我判据里补的词"当成"技能已覆盖");④ **§9.4 撞点分恶性/良性** —— 只有"偏向错误一方"才是缺陷,互补分工的撞不要消;⑤ §9.5 两条自踩的坑(改 frontmatter 必数字段出现次数;`python -c` 带反引号会被 shell 吃掉)。顺带补齐缺失的 `last_change` 字段。
|
||||
agent_created: true
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 变更历史(**对侧副本** frontmatter · 逐字保留 · 来自 `dsh-knowledge-upkeep` 的 文档库 版)
|
||||
|
||||
> ⚠️ 本档正文取自**另一侧**(超集);此处补上对侧副本的版本史,⛔ 以保证不丢任何事实。
|
||||
|
||||
```text
|
||||
name: dsh-knowledge-upkeep
|
||||
description: 项目知识与文档库的维护纠偏方法:知识分层、实体与指针的取舍、漂移处理、注入预算治理(超限先重排后删减)、技能集可用性体检。触发场景:发现文档与现状不符、同一事实多处打架、知识越积越碎、AI 忘了某条规则、注入被静默截断,或要收敛重构知识库结构时。
|
||||
version: 1.0.0
|
||||
updated_at: 2026-09-24
|
||||
last_change: 【2026-09-24 新增 §11「归档镜像结构治理与双端对账」—— 本线完成服务器 `/opt/dsh/docs` 归档镜像的结构改造(A 方案)后沉淀:三分表判定法(映射后路径 × 内容交叉分类)· 负向后顾正则(裸 grep 会把新前缀 `04-调整方案/` 命中 ⇒ 假阳性)· 两端比对忽略行尾 · 改名后必查六项(功能性常量 / 自检脚本前缀 / 条件规则 paths / 生成物重建 / 对账排除项 / 技能自指)· 改造安全姿势(全量 tar + 旧结构 mv 归档,⛔ 不 rm)。】先前 2026-09-22 按要求统一版本号】frontmatter `version` → `1.0.0`(原 v1.4.0);正文与历史中的版本号为当时记录,未改动。此前 v1.4.0(2026-09-22):新增 **§9「技能集可用性体检(10 层取证法)」** —— 起因:用户问「这么多 dsh 技能会不会话能准确使用 + 后续方便维护」,用 10 层递进取证回答。要点:① 体检对象是 **description 的区分度**(技能按需匹配加载);② **§9.2 最高价值层 = 查"用户明确定过的规则有没有技能接得住"** —— 实测 12 个技能里 7 条规则无任何 description 命中(「只做被明确要求的事」历史原话出现 62 次且已写在本技能正文里,却接不住);③ **§9.3 判据漏词会伪装成技能盲区**(55.8% → 42.9% 全是我的关键词表不全,⛔ 不许把"我判据里补的词"当成"技能已覆盖");④ **§9.4 撞点分恶性/良性** —— 只有"偏向错误一方"才是缺陷,互补分工的撞不要消;⑤ §9.5 两条自踩的坑(改 frontmatter 必数字段出现次数;`python -c` 带反引号会被 shell 吃掉)。顺带补齐缺失的 `last_change` 字段。
|
||||
agent_created: true
|
||||
```
|
||||
@@ -0,0 +1,260 @@
|
||||
# 过程文档与架构定稿的分层机制
|
||||
|
||||
> **归属**:技能 `dsh-knowledge` · 详情档(主干 `../SKILL.md`)
|
||||
> **本档覆盖**:原技能 `dsh-architecture-lifecycle` **全文**(正文 164 行 + frontmatter 变更历史)
|
||||
> **provenance**:本机版(WorkBuddy 实况)
|
||||
> **搬运方式**:**逐行未改**;内部附件链接已改写为 `dsh-architecture-lifecycle/<附件名>`(附件在本目录下)
|
||||
> ⚠️ 原技能 `dsh-architecture-lifecycle` **已合并退役** ⇒ 见到该名按本档读。
|
||||
|
||||
---
|
||||
|
||||
|
||||
# dsh-architecture-lifecycle — 过程 / 定稿分层机制
|
||||
|
||||
> **一句话**:**过程文档记录「当时怎么想的」,架构定稿声明「现在该做成什么样」**。两者混放 ⇒ 后续会话读到的是**过时结论**,而且**读全的概率趋近于零**。
|
||||
> 素材来源:2026-09-21 用户三次纠正(「现在调整方案都是过程信息,后续 AI 会话找的时候可能看不全」→「过程和定稿要分开放」→「所有架构放一个文件夹,具体什么架构体现在文件名上」)。
|
||||
|
||||
---
|
||||
|
||||
## 0. 为什么需要它(用户原话 + 实测)
|
||||
|
||||
| 现象 | 实测 |
|
||||
|---|---|
|
||||
| **架构结论散落在过程档案里** | 覆盖网络顶层架构的结论分散在 **103–117 / 118–120 / 133 / 136 / 145–147 / 148 / 149 / 序③交接单**,共 **20+ 份**档案 |
|
||||
| **后续会话「看不全」** | 用户原话:**「现在调整方案都是过程信息,后续 AI 会话找的时候可能看不全」** |
|
||||
| **同一架构有多版口径** | 148 提出的形态被 149 **修正两处过强表述** ⇒ 只读 148 会得到**错的架构** |
|
||||
| **过程档案会自然变长** | 入口文档实测 `§0 = 32%` 是累积「🆕刷新」块、`§2 = 60%` 是累积「上一轮已完成」块 ⇒ **真信号被埋** |
|
||||
| **入口曾指向不存在的文件** | 某入口 `§1` 列着 **10 个文件名**,都已随「加编号前缀」改动而**改名** ⇒ 照它去找 = 找不到 |
|
||||
|
||||
**根因**:没有区分「**过程**」与「**成品**」。两者默认同目录、同命名、同生命周期 ⇒
|
||||
**过程档案每更新一次,架构的权威表述就多一份副本**,且没有一份声明「以我为准」。
|
||||
|
||||
> ⚠️ 用户第一次纠正时的措辞值得记住:**「网络架构的信息应该有一份总的最终的不断迭代」**。
|
||||
> 关键词是 **「一份」「总的」「最终的」「不断迭代」** ——
|
||||
> **一份**(不是散在多处)· **总的**(覆盖全貌,不是单点结论)· **最终的**(成品,不是过程)· **不断迭代**(定稿本身会演进,不是冻结)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 两个目录,一个判据(🔴 本技能的核心)
|
||||
|
||||
| | **过程目录**(本项目 = `04-调整方案/`) | **定稿目录**(本项目 = `02-架构设计/`) |
|
||||
|---|---|---|
|
||||
| **装什么** | 调研、推演、取证、权衡、待拍板 | 收敛后的架构设计 |
|
||||
| **形态** | **逐个问题一档**,编号递增(100–150…) | **按架构成篇**,文件名=架构名 |
|
||||
| **命名** | `<NN>-<主题>.md`(编号是**过程**的标识) | `<对象>-<架构名>.md`(**⛔ 不用编号前缀**) |
|
||||
| **会不会过时** | **会** —— 需求变则结论变 | **不轻易变** —— 只有架构本身变了才改 |
|
||||
| **读它的目的** | 追根因、查证据、看权衡 | **照此施工 / 对外介绍 / 新人理解全局** |
|
||||
| **可否删** | ⛔ 不可(是历史与判据来源) | ⛔ 不可(是当前唯一权威) |
|
||||
|
||||
### 1.1 唯一判据(落到每一份文件上)
|
||||
|
||||
> **「这份文档是在记录『我们怎么想到的』,还是在声明『现在该做成什么样』?」**
|
||||
>
|
||||
> 前者 ⇒ **过程目录**;后者 ⇒ **定稿目录**。
|
||||
|
||||
**反向判据(防误放)**:
|
||||
- 含**多个候选方案 / 优缺点对比 / 未拍板项** ⇒ **过程**(还没收敛)
|
||||
- 含**「倾向」「建议」「拟」** ⇒ **过程**(还没定)
|
||||
- 含**`file:line` 取证** ⇒ **过程**(证据属推演过程;定稿只引结论)🔴 **例外:定稿要保留「该结论的依据在哪份档案」的回溯指针**
|
||||
- 一份文档只讲**一个架构的全貌**、可直接照着施工 ⇒ **定稿**
|
||||
|
||||
### 1.2 冲突仲裁(🔴 必须写进两边)
|
||||
|
||||
> **定稿目录与过程档案冲突时,一律以定稿目录为准。**
|
||||
|
||||
**仲裁后要做的**:在**过程档案头部加状态块**指向定稿,⛔ **不要改过程档案正文**。
|
||||
理由见 §4。
|
||||
|
||||
---
|
||||
|
||||
## 2. 定稿目录的命名约定(用户第二次纠正的落点)
|
||||
|
||||
> 用户原话:**「所有的架构放在一个文件夹中 方便管理,具体什么架构体现在文件名称上」**
|
||||
|
||||
### 2.1 铁律三条
|
||||
|
||||
1. 🔴 **一个目录装全部架构** —— ⛔ **不再按架构分子目录**(用户第一次我做成 `<对象>-顶层架构/` 子目录,被纠正)。
|
||||
2. 🔴 **文件名 = 架构名** —— 命名 `<对象>-<架构名>.md`,**看文件名就知道该不该打开**。
|
||||
3. 🔴 **⛔ 不用编号前缀** —— 编号是**过程**的标识;定稿目录内**按文件名排序**即可。
|
||||
|
||||
### 2.2 `<对象>` 与 `<架构名>` 各自是什么
|
||||
|
||||
- **对象** = 被设计的那个系统部分:`覆盖网络` / `平台` / `插件体系` / `客户端` …
|
||||
- **架构名** = 这份文档讲的那个架构:`顶层架构全貌` / `多区域联邦` / `密钥与信任` …
|
||||
|
||||
**示例**:`覆盖网络-顶层架构全貌.md` = 讲**覆盖网络的顶层架构全貌**。
|
||||
|
||||
### 2.3 反例(别这样命名)
|
||||
|
||||
| ❌ 错 | 为什么 |
|
||||
|---|---|
|
||||
| `01-顶层架构全貌.md` | 编号前缀 ⇒ 与过程档案混同,且排序变成"谁先写"而非"谁是什么" |
|
||||
| `覆盖网络/顶层架构全貌.md` | 按对象分子目录 ⇒ 违反「一个目录装全部」 |
|
||||
| `架构.md` | 文件名没说是什么架构 ⇒ 看名字无法判断该不该打开 |
|
||||
| `149-顶层架构全貌定稿.md` | 带过程档案号 ⇒ 两套编号体系打架 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 收敛(过程 → 定稿)五步
|
||||
|
||||
> **定稿不是新写的,是从过程档案收敛而来。** 收敛 ≠ 复制粘贴,而是**判定 + 合并 + 消解冲突**。
|
||||
|
||||
```
|
||||
① 定位 → 列出与该架构有关的所有过程档案(grep 文件名 / 关键词 / 目录)
|
||||
② 判新旧 → 逐份看「谁修正了谁」;🔴 后出的档案若显式修正了先出的,以新为准
|
||||
③ 合并 → 按「架构自己的结构」重组(不是按档案顺序堆叠)
|
||||
④ 标回溯 → 每条结论标注来源档案号(便于日后追根因)
|
||||
⑤ 标未决 → 尚未拍板的一律**显式列为待决项**,⛔ 不要悄悄写成已定
|
||||
```
|
||||
|
||||
### 3.1 ② 判新旧是**分水岭**(实测踩过)
|
||||
|
||||
覆盖网络线实测:**148** 首次提出「多 Manager 多区域联邦」,**149** 又**修正了 148 的两处过强表述**。
|
||||
⇒ 若只读 148 就写定稿,会把**已被推翻的表述**当成结论**固化进定稿**(更糟:定稿的权威性会让人不再回查)。
|
||||
|
||||
**判据**:档案头部/正文里搜 **「修正」「订正」「本节更正」「此前表述过强」** 等措辞;
|
||||
有 ⇒ 它是**后出且覆盖前者**的。
|
||||
|
||||
### 3.2 ③ 按「架构自己的结构」重组
|
||||
|
||||
⛔ **不要按档案顺序堆叠**(那只是把过程搬到另一个目录)。
|
||||
✅ **按架构的内在结构分节**,例如顶层架构全貌 = ①是什么 ②两张图分离 ③三层权威 ④多根与角色升格 ⑤接入路径 ⑥缺口 ⑦推进序 ⑧待决项。
|
||||
|
||||
### 3.3 ⑤ 未决项必须显式(🔴 红线)
|
||||
|
||||
**未拍板的事,在定稿里必须是「待决项」小节,⛔ 不得写成已完成的口径。**
|
||||
否则后续会话会把「AI 的倾向」当成「用户已批准的决定」去施工 —— 这是**最贵的一类错误**。
|
||||
|
||||
**格式**:逐条编号 + 每个候选写**优点/缺点** + 写明**必须在哪个节点前拍板**(若有依赖)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 过时档案的处理:只加「状态块」,⛔ 不改正文
|
||||
|
||||
**触发**:某份过程档案的结论**已被定稿取代**。
|
||||
|
||||
**做法**:在**该档案头部**插入状态块:
|
||||
|
||||
```markdown
|
||||
> **⚠️ 本文结论已被取代** → 见 `02-架构设计/<对象>-<架构名>.md`(定稿,YYYY-MM-DD)。
|
||||
> 本文保留为**过程记录**,正文不改(档案 = 当时的事实)。
|
||||
```
|
||||
|
||||
### 4.1 为什么⛔ 不改正文
|
||||
|
||||
| 理由 | 说明 |
|
||||
|---|---|
|
||||
| **正文 = 当时的事实** | 改了就不再是「当时怎么想的」,回溯链断裂 |
|
||||
| **改动会造新错误** | 事后改正文要重新推理,等于**用今天的认知重写昨天的证据** |
|
||||
| **省事但危险** | 只加 3 行状态块 vs 重写全篇;后者还可能引入事实错误 |
|
||||
|
||||
> 与 `dsh-knowledge-upkeep` §1 铁律 2(**L5 冻结**)同源:**历史档案不回改**。
|
||||
> 补充判据:**「修正」写在定稿里,「指向」写在过程档案头部** —— 两边各司其职。
|
||||
|
||||
### 4.2 ⛔ 不要在两处同时改
|
||||
|
||||
**入口文档 / 台账只允许一份**,⛔ 禁止「两处同改」的双份维护(双份必然漂移)。
|
||||
发现双份 ⇒ **先合并成一份**,另一处改为**指针**。
|
||||
|
||||
---
|
||||
|
||||
## 5. 定稿的三道验收(贴出去之前过一遍)
|
||||
|
||||
| # | 验收项 | 判据 |
|
||||
|---|---|---|
|
||||
| **A** | **可独立读懂** | 只读定稿、⛔ 不点开任何过程档案,能否答出「这是什么、现在该做成什么样」 |
|
||||
| **B** | **可照此施工** | 结论是否**收敛到可执行**(有对象、有动作、有边界);仍有「倾向/建议/待定」的 ⇒ 移入待决项 |
|
||||
| **C** | **可回溯** | 关键结论能否指回过程档案号;待决项是否标明**必须在哪个节点前拍板** |
|
||||
|
||||
⚠️ **A 不过 = 定稿变成了「过程索引」**(最常见失败模式:把所有过程摘要拼一起,读它还得读别的)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 定稿目录自身的 README 必须写什么
|
||||
|
||||
每个定稿目录**必须有 `README.md`**,且至少含:
|
||||
|
||||
1. **一句话定位** —— 这个目录装什么(成品,非过程)
|
||||
2. **与过程目录的分工表** —— 见 §1 那张表(可直接复制)
|
||||
3. **唯一判据** —— 见 §1.1
|
||||
4. **命名约定** —— 见 §2
|
||||
5. **当前内容清单** —— 「文档 / 讲什么 / 定稿日期」三列
|
||||
6. **来源(过程依据)** —— 列出收敛自哪些档案;🔴 写明**冲突以本目录为准**
|
||||
7. **过时档案处理规则** —— 见 §4
|
||||
|
||||
---
|
||||
|
||||
## 7. 与既有技能的分工(别抢活)
|
||||
|
||||
| 技能 | 管什么 | 与本技能的关系 |
|
||||
|---|---|---|
|
||||
| **`dsh-knowledge-upkeep`** | 知识库**怎么不变错**(六层结构 / 漂移 SOP / Lint 四件套) | 本技能是它的**架构级特化**:它在 L5 只写「`04-调整方案/` 只增不改」,**未定义「定稿层」** ⇒ 本技能补上 L0.5「定稿」层与收敛流程 |
|
||||
| **`session-mechanism`** | 单个决策**怎么定得对** | 本技能处理**决策沉淀为定稿**的那一段(§3) |
|
||||
| **`dsh-change-workflow`** | 改造**怎么落地** | 定稿是「落地前的目标形态」;施工按 `dsh-change-workflow` |
|
||||
|
||||
### 7.1 给 `dsh-knowledge-upkeep` 的层表补充(建议)
|
||||
|
||||
原六层(L0 不变量 / L1 现行值 / L2 规则 / L3 方法 / L4 状态 / L5 历史)中,
|
||||
**架构定稿**介于 L0 与 L1 之间:
|
||||
|
||||
| 层 | 内容 | 唯一权威 | 变更频率 |
|
||||
|---|---|---|---|
|
||||
| **L0.5 架构定稿** | 架构级的「现在该做成什么样」 | **`02-架构设计/`** | **中**(架构变才改) |
|
||||
|
||||
⇒ L0(不变量:为什么这样设计)与 L0.5(该做成什么样)的差别:
|
||||
**L0 是「为什么」,L0.5 是「是什么/做什么」**;L0.5 比 L0 变得勤,比 L1(现值)稳。
|
||||
|
||||
---
|
||||
|
||||
## 8. 自检(动手前 / 收尾)
|
||||
|
||||
**动手前**
|
||||
1. 我要写的是**过程**还是**定稿**?(§1.1 判据)
|
||||
2. 若是定稿:**收敛自哪些过程档案?谁修正了谁?**(§3.1)
|
||||
3. 里面有没有**未拍板**的事被我写成了已定?(§3.3 红线)
|
||||
|
||||
**收尾**
|
||||
4. 定稿是否**只靠它自己就能读懂**?(验收 A)
|
||||
5. 文件名是否符合 `<对象>-<架构名>.md`、⛔ 无编号前缀?(§2)
|
||||
6. 被取代的过程档案是否加了**状态块**(而非改正文)?(§4)
|
||||
7. 定稿目录 README 的**内容清单**是否登记了新文档?(§6)
|
||||
8. **同一事实是否仍有第二处副本**?(§4.2)
|
||||
|
||||
---
|
||||
|
||||
## 9. 反例(2026-09-21 真实踩过)
|
||||
|
||||
| # | 反例 | 后果 | 规避 |
|
||||
|---|---|---|---|
|
||||
| 1 | 先按对象做成 `<对象>-顶层架构/` **子目录** | 被用户纠正 ⇒ 返工重排 | §2.1 铁律 1:**一个目录装全部** |
|
||||
| 2 | 定稿文件叫 `01-顶层架构全貌.md` | 被用户纠正 ⇒ 编号属过程,定稿不用 | §2.1 铁律 3 |
|
||||
| 3 | 差点只读 148 就写定稿 | 148 有两处已被 149 修正 ⇒ 会固化错结论 | §3.1 判新旧 |
|
||||
| 4 | 过程档案里曾出现「两处同改」的双份入口 | 双份必然漂移,别的工作区会误判归属 | §4.2 入口只一份 |
|
||||
| 5 | 入口文档累积历史,`§0+§2` 占 92% | 真信号被埋 ⇒ 「后续会话看不全」的直接成因 | 过程目录**也要控制累积**;历史压到归档 |
|
||||
| 6 | 入口 `§1` 列了 10 个**已改名**的文件名 | 照它去找 = 找不到 | 列文件时用**可复跑**的引用(`INDEX.md` / `ls`),⛔ 不写死名单 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 判据速查(一屏)
|
||||
|
||||
```
|
||||
① 这份文档是「怎么想的」还是「该做成什么样」? → 过程 / 定稿
|
||||
② 定稿命名:<对象>-<架构名>.md,⛔ 无编号前缀,一个目录
|
||||
③ 收敛五步:定位 → 判新旧 → 按架构重组 → 标回溯 → 标未决
|
||||
④ 冲突:以定稿为准;过程档案只加状态块,⛔ 不改正文
|
||||
⑤ 未拍板的事 ⛔ 不得写成已定 —— 必须单列待决项
|
||||
⑥ 定稿验收:可独立读懂 / 可照此施工 / 可回溯
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 变更历史(原 frontmatter · 逐字保留)
|
||||
|
||||
```text
|
||||
name: dsh-architecture-lifecycle
|
||||
description: DSH 平台「**过程文档与架构定稿的分层机制**」—— 治「架构结论散落在几十份过程档案里、后续会话找不到、同一事实多处打架」。当要**写架构级设计**、要**把推演结论定稿**、要**判断某份文档该放过程还是定稿**、要**查「现在的架构到底是什么样」**、或发现**同一架构有多处互相矛盾的表述**时使用。核心 = 两个目录的**唯一分工判据** + 定稿命名约定 + **收敛(过程→定稿)五步** + 冲突仲裁 + 过时档案的**状态块**纪律 + 定稿的三道验收。**配套:`dsh-knowledge-upkeep`(知识库怎么不变错)· `session-mechanism`(怎么定得对)· `dsh-change-workflow`(怎么落地)。**
|
||||
version: 1.0.0
|
||||
updated_at: 2026-09-21
|
||||
agent_created: true
|
||||
```
|
||||
@@ -0,0 +1,109 @@
|
||||
# 技能重组 · 千行技能拆分(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/`。
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""split_skill.py — 技能重组:把千行级技能的 SKILL.md 拆成「主干 + references/ 详情档」。
|
||||
|
||||
用法:
|
||||
<python> split_skill.py --spec <spec.json> [--dry-run]
|
||||
|
||||
设计(照 `dsh-knowledge-upkeep` §10):
|
||||
· **纯机械搬运** —— 只按行号区间搬移,逐行未改;不重写、不润色、不删字。
|
||||
· **可重复跑(幂等)** —— 永远以 `<backup_dir>/<skill>-SKILL.md` 为拆前基准;
|
||||
首次运行自动生成该备份。⚠️ 绝不以「活文件」为基准(否则第二次跑会二次切割)。
|
||||
· **四项自校验**:① 行数守恒 ② 逐行包含 0 丢失 ③ 节可寻址 ④ frontmatter 唯一。
|
||||
|
||||
spec.json:
|
||||
{
|
||||
"skills_dir": "E:/ProgramData/.workbuddy/skills",
|
||||
"backup_dir": "…/tmp/_keep-YYYYMMDD/split-bak",
|
||||
"skill": "dsh-change-workflow",
|
||||
"main_desc": "§1 … · §2 …", // 写进详情档头的「主文件」说明
|
||||
"moves": [
|
||||
{"ranges": [[14,92]], "ref": "00-平台速查.md",
|
||||
"title": "平台速查(硬编码事实,勿猜)",
|
||||
"covers": ["平台速查(硬编码事实,勿猜)"], // 用于主干索引表的「覆盖的原章节」列
|
||||
"pointer": "> 📂 **…已下沉** → `references/00-平台速查.md`"}
|
||||
]
|
||||
}
|
||||
ranges = 1-based 闭区间,可多段,**不得重叠**;pointer 支持 \n 多行。
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import Counter
|
||||
|
||||
HDR = ("# {title}\n\n"
|
||||
"> **归属**:技能 `{skill}` 的详情档(**按需读**,不是每次都要读)。\n"
|
||||
"> **本档覆盖**:{covers}(原行 {rng})。\n"
|
||||
"> **主文件 / 判据与流程主干** = `../SKILL.md`({main})。\n"
|
||||
"> **来源**:技能重组把 `../SKILL.md` 的 {rng} 段**逐行原样**下沉到本文件,未改一字。\n"
|
||||
"> **跨档引用**:正文里的「§N / 见 §8 坑 N / 见下表」等编号,用 `../SKILL.md` 末节「详情索引」的原章节列定位。\n\n---\n")
|
||||
|
||||
md5 = lambda b: hashlib.md5(b).hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--spec", required=True)
|
||||
ap.add_argument("--dry-run", action="store_true")
|
||||
a = ap.parse_args()
|
||||
|
||||
cfg = json.load(open(a.spec, encoding="utf-8"))
|
||||
skill = cfg["skill"]
|
||||
d = os.path.join(cfg["skills_dir"], skill)
|
||||
src = os.path.join(d, "SKILL.md")
|
||||
os.makedirs(cfg["backup_dir"], exist_ok=True)
|
||||
bak = os.path.join(cfg["backup_dir"], "%s-SKILL.md" % skill)
|
||||
|
||||
if not os.path.exists(bak): # 首次:以活文件为基准建备份
|
||||
open(bak, "wb").write(open(src, "rb").read())
|
||||
orig_bytes = open(bak, "rb").read() # 之后永远以备份为基准(幂等)
|
||||
orig = orig_bytes.decode("utf-8").split("\n")
|
||||
if orig and orig[-1] == "":
|
||||
orig = orig[:-1]
|
||||
n = len(orig)
|
||||
|
||||
# ── 区间校验
|
||||
moved, starts = {}, {}
|
||||
for mv in cfg["moves"]:
|
||||
for rng in mv["ranges"]:
|
||||
lo, hi = rng
|
||||
assert 1 <= lo <= hi <= n, "区间越界 %s(原文件 %d 行)" % (rng, n)
|
||||
for i in range(lo, hi + 1):
|
||||
assert i not in moved, "区间重叠于第 %d 行" % i
|
||||
moved[i] = mv["ref"]
|
||||
starts[min(r[0] for r in mv["ranges"])] = mv
|
||||
|
||||
# ── 生成主干
|
||||
out, ptrs = [], []
|
||||
for i in range(1, n + 1):
|
||||
if i in starts:
|
||||
out.append(starts[i]["pointer"])
|
||||
out.append("")
|
||||
ptrs.append(starts[i]["pointer"])
|
||||
if i not in moved:
|
||||
out.append(orig[i - 1])
|
||||
main_len = len(out)
|
||||
|
||||
# ── 生成详情档
|
||||
refs = {}
|
||||
for mv in cfg["moves"]:
|
||||
refs.setdefault(mv["ref"], []).append(mv)
|
||||
index, ref_body = [], {}
|
||||
for refname, mvs in sorted(refs.items()):
|
||||
rngs = sorted(r for mv in mvs for r in mv["ranges"])
|
||||
body = [l for (lo, hi) in rngs for l in orig[lo - 1:hi]]
|
||||
rng_txt = " + ".join("L%d–L%d" % (lo, hi) for (lo, hi) in rngs)
|
||||
covers = " · ".join(dict.fromkeys(c for mv in mvs for c in mv.get("covers", [])))
|
||||
head = HDR.format(title=mvs[0]["title"], skill=skill, covers=covers,
|
||||
rng=rng_txt, main=cfg.get("main_desc", ""))
|
||||
ref_body[refname] = body
|
||||
index.append((refname, covers, len(body), rng_txt))
|
||||
if not a.dry_run:
|
||||
rd = os.path.join(d, "references")
|
||||
os.makedirs(rd, exist_ok=True)
|
||||
with open(os.path.join(rd, refname), "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(head + "\n".join(body) + "\n")
|
||||
|
||||
# ── 主干末节:详情索引(= 跨档引用的定位表)
|
||||
out += ["", "## 详情索引(references/)", "",
|
||||
"> 本技能 = **主干(本文件)+ 详情档**。主干只留判据 / 流程主干 / 命令骨架;长表、案例、实测记录、历史细节在下面各档。",
|
||||
">", "> **跨档引用怎么查**:正文里的「§N」「见 §8 坑 N」「见下表」等编号,按本表「覆盖的原章节」列定位。",
|
||||
"", "| 详情档 | 覆盖的原章节 | 原行段 | 行数 |", "|---|---|---|---|"]
|
||||
for refname, covers, ln, rng_txt in index:
|
||||
out.append("| `references/%s` | %s | %s | %d |" % (refname, covers.replace("|", "/"), rng_txt, ln))
|
||||
out.append("")
|
||||
idx_block = len(out) - main_len
|
||||
text = "\n".join(out).rstrip("\n") + "\n"
|
||||
new_lines = text.split("\n")[:-1]
|
||||
|
||||
# ── 四项自校验
|
||||
core = main_len - 2 * len(cfg["moves"])
|
||||
det = sum(len(v) for v in ref_body.values())
|
||||
A = Counter(orig)
|
||||
B = Counter([l for l in new_lines if l not in ptrs]
|
||||
+ [l for rn in ref_body for l in ref_body[rn]])
|
||||
lost = [k for k in A if A[k] > B[k]]
|
||||
heads = [re.sub(r"^#+\s*", "", l).strip() for l in orig if re.match(r"^#{1,3} ", l)]
|
||||
blob = text + "".join("\n".join(v) for v in ref_body.values())
|
||||
miss = [h for h in heads if h and h not in blob]
|
||||
fm = [l for l in new_lines[:14] if l.startswith("version:")]
|
||||
|
||||
checks = [
|
||||
("① 行数守恒", core + det == n, "主干内容 %d + 详情 %d = %d(原 %d)" % (core, det, core + det, n)),
|
||||
("② 逐行包含", not lost, "丢失 %d 行;新增(指针 %d + 索引 %d)" % (len(lost), 2 * len(cfg["moves"]), idx_block)),
|
||||
("③ 节可寻址", not miss, "标题 %d 个,未命中 %d%s" % (len(heads), len(miss), (" " + repr(miss[:3])) if miss else "")),
|
||||
("④ frontmatter", len(fm) == 1, "version 行 = %s" % (fm or "缺失/重复")),
|
||||
]
|
||||
print("=" * 68)
|
||||
print("技能 %s:原 %d 行 md5=%s" % (skill, n, md5(orig_bytes)))
|
||||
for name, ok, info in checks:
|
||||
print(" %s %s ── %s" % ("OK " if ok else "FAIL", name, info))
|
||||
print(" 主干 SKILL.md = %d 行 %s" % (len(new_lines), "(dry-run,未落盘)" if a.dry_run else "md5=%s" % md5(text.encode("utf-8"))))
|
||||
for refname, covers, ln, rng_txt in index:
|
||||
print(" - %-40s %4d 行 %s" % (refname, ln, rng_txt))
|
||||
if not a.dry_run:
|
||||
with open(src, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(text)
|
||||
bad = [c[0] for c in checks if not c[1]]
|
||||
if bad:
|
||||
print("⛔ 未通过:%s ⇒ 停下来看,别硬推" % "、".join(bad))
|
||||
return 1
|
||||
print("✅ 四项校验全绿" + ("(dry-run)" if a.dry_run else ";记得三处同步 + 三方 md5 对账"))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user