dsh-skill-folder 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -0
- package/cordis.patch.yml +31 -0
- package/docs/class-diagram.mermaid +34 -0
- package/docs/refactoring-plan-v2.md +237 -0
- package/docs/refactoring-plan-v3-final.md +284 -0
- package/docs/sequence-diagram.mermaid +17 -0
- package/docs/skill-metadata-schema.md +49 -0
- package/docs/system_design.md +299 -0
- package/lib/bm25.js +115 -0
- package/lib/catalog.js +133 -0
- package/lib/index.js +215 -0
- package/lib/pattern.js +23 -0
- package/lib/quality-scorer.js +191 -0
- package/lib/render.js +102 -0
- package/lib/select.js +66 -0
- package/lib/semantic.js +239 -0
- package/lib/skill-search.js +155 -0
- package/lib/tool-skill-search.js +92 -0
- package/package.json +50 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# 技能系统灵活性根治方案(v3 验证通过版)
|
|
2
|
+
|
|
3
|
+
**最终修订**: 2026-08-28
|
|
4
|
+
**验证状态**: ✅ 通过 `dsh-verification` 审查(7.4/10 → 修正后 9/10)
|
|
5
|
+
**实施原则**: 实测驱动、单变量修改、有回滚方案
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 执行摘要
|
|
10
|
+
|
|
11
|
+
基于基线测试(BM25 命中率 85%、质量平均分 9.7/25、100% 无测试)和验证审查,本方案聚焦**3 个高收益低风险**的改进:
|
|
12
|
+
|
|
13
|
+
| 优先级 | 方案 | 预期收益 | 实施成本 | 风险 | 回滚方案 |
|
|
14
|
+
|---|---|---|---|---|---|
|
|
15
|
+
| **P0** | 1a: 描述本地化 | BM25 85%→95% | 1 天 | 低 | 恢复原描述 |
|
|
16
|
+
| **P1** | 2: 元数据增强 | 支持路由/评分 | 2 天 | 中 | 忽略新字段 |
|
|
17
|
+
| **P2** | 3: 质量建议(非门控) | 质量透明化 | 1.5 天 | 低 | 隐藏评分 |
|
|
18
|
+
|
|
19
|
+
**删除方案**:
|
|
20
|
+
- ~~方案 4(触发条件验证)~~——实测证明"描述 - 内容不一致"是设计特性
|
|
21
|
+
- ~~方案 5(懒加载)~~——用户未提此需求,优先级低于其他
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 方案 1a: 描述本地化(P0,1 天)
|
|
26
|
+
|
|
27
|
+
### 问题根因(实测)
|
|
28
|
+
BM25 miss 的 3 个案例:
|
|
29
|
+
1. "安全审计" → `dsh-sec-code-audit` 描述为英文,不含"审计"
|
|
30
|
+
2. "创建插件" → `cordis-plugin-development` 是系统技能,不在 skills 目录
|
|
31
|
+
3. "思维导图" → `dsh-sec-diagram-generator` 描述含"mind maps"无中文
|
|
32
|
+
|
|
33
|
+
**结论**: 2/3 是**英文描述不含中文关键词**,1/3 是系统技能(无需改)。
|
|
34
|
+
|
|
35
|
+
### 实施方案
|
|
36
|
+
**不改 BM25 算法,改技能描述**:
|
|
37
|
+
1. 扫描所有技能,识别英文描述
|
|
38
|
+
2. 批量补中文关键词(半自动)
|
|
39
|
+
3. 重跑 BM25 测试验证
|
|
40
|
+
|
|
41
|
+
### 实施步骤
|
|
42
|
+
```bash
|
|
43
|
+
# Step 1: 扫描英文描述技能(输出清单)
|
|
44
|
+
node test/scan-english-desc.js > test/english-desc-list.txt
|
|
45
|
+
|
|
46
|
+
# Step 2: 批量补中文关键词(编辑 SKILL.md)
|
|
47
|
+
# 例:description: "Use for authorized source-code security review..."
|
|
48
|
+
# → "代码安全审计。Use for authorized source-code security review..."
|
|
49
|
+
|
|
50
|
+
# Step 3: 验证命中率提升至≥95%
|
|
51
|
+
node test/baseline-bm25.js
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 验收标准
|
|
55
|
+
- [ ] BM25 中文命中率 ≥ 95%(20 个测试查询命中≥19 个)
|
|
56
|
+
- [ ] 不新增运行时依赖
|
|
57
|
+
- [ ] 不改变检索逻辑
|
|
58
|
+
- [ ] **回滚测试**: 恢复原描述后命中率回退到 85%
|
|
59
|
+
|
|
60
|
+
### 工作量估算
|
|
61
|
+
- 扫描脚本:0.5 天
|
|
62
|
+
- 批量编辑:0.5 天(预计 30-50 个技能需改)
|
|
63
|
+
- 验证测试:0.25 天
|
|
64
|
+
- **总计**: 1.25 天 → **1 天**(并行执行)
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 方案 2: 元数据增强(P1,2 天)
|
|
69
|
+
|
|
70
|
+
### 问题根因(实测)
|
|
71
|
+
技能无统一 metadata schema,导致:
|
|
72
|
+
- 无法自动路由(无 scope 字段)
|
|
73
|
+
- 无法质量评分(无 tested 字段)
|
|
74
|
+
- 版本管理困难(version 字段无校验)
|
|
75
|
+
|
|
76
|
+
### 实施方案
|
|
77
|
+
**扩展现有 frontmatter,向后兼容**:
|
|
78
|
+
```yaml
|
|
79
|
+
---
|
|
80
|
+
name: dsh-debugging
|
|
81
|
+
version: 1.0.0
|
|
82
|
+
description: 系统化调试一体化技能...
|
|
83
|
+
# 新增字段(可选,向后兼容)
|
|
84
|
+
scope: [debugging, testing]
|
|
85
|
+
tested: false
|
|
86
|
+
createdAt: 2026-08-01
|
|
87
|
+
updatedAt: 2026-08-28
|
|
88
|
+
---
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**关键设计决策**:
|
|
92
|
+
- 新字段**可选**(存量技能不强制补全)
|
|
93
|
+
- `scope` 用于路由(多技能匹配时选 scope 最特异的)
|
|
94
|
+
- `tested` 用于质量评分(有测试的技能加分)
|
|
95
|
+
- `createdAt/updatedAt` 从 git 历史自动提取
|
|
96
|
+
|
|
97
|
+
### 实施步骤
|
|
98
|
+
```bash
|
|
99
|
+
# Step 1: 定义 schema(lib/schema.js)
|
|
100
|
+
# Step 2: 写验证脚本(test/validate-metadata.js)
|
|
101
|
+
# Step 3: 存量技能自动补 updatedAt(从 git)
|
|
102
|
+
# Step 4: 新增技能强制校验(CI 门控)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 验收标准
|
|
106
|
+
- [ ] schema 定义完成(lib/schema.js)
|
|
107
|
+
- [ ] 验证脚本通过(test/validate-metadata.js)
|
|
108
|
+
- [ ] 存量技能 100% 有 updatedAt(自动提取)
|
|
109
|
+
- [ ] 新增技能强制校验 name/description/version
|
|
110
|
+
- [ ] **回滚测试**: 忽略新字段后系统正常工作
|
|
111
|
+
|
|
112
|
+
### 工作量估算
|
|
113
|
+
- Schema 定义:0.5 天
|
|
114
|
+
- 验证脚本:0.5 天
|
|
115
|
+
- 存量补全:0.5 天(自动脚本)
|
|
116
|
+
- CI 集成:0.5 天
|
|
117
|
+
- **总计**: 2 天
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 方案 3: 质量建议(P2,1.5 天)
|
|
122
|
+
|
|
123
|
+
### 问题根因(实测)
|
|
124
|
+
- 质量平均分 9.7/25(60% 低于 10 分)
|
|
125
|
+
- 100% 技能无测试目录
|
|
126
|
+
- **但技能是文档,"测试"可能是范畴错误**
|
|
127
|
+
|
|
128
|
+
### 实施方案(修正版)
|
|
129
|
+
**质量评分作为建议,非门控**:
|
|
130
|
+
1. 定义评分维度(结构完整性、示例质量、更新频率、描述本地化)
|
|
131
|
+
2. `skill_search` 返回带 `qualityScore` 字段
|
|
132
|
+
3. **不强制**,仅透明化
|
|
133
|
+
|
|
134
|
+
**评分维度(修正后)**:
|
|
135
|
+
| 维度 | 分值 | 说明 |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| 结构完整性 | 0-10 | When to Use + Core Pattern + Examples |
|
|
138
|
+
| 示例质量 | 0-5 | 代码块数量(0=0 分,1-2=3 分,3+=5 分) |
|
|
139
|
+
| 更新频率 | 0-5 | 30 天内=5 分,90 天=3 分,180 天=2 分,>180=1 分 |
|
|
140
|
+
| 描述本地化 | 0-5 | 含中文关键词=5 分,纯英文=0 分 |
|
|
141
|
+
| **总分** | **0-25** | |
|
|
142
|
+
|
|
143
|
+
**删除"测试覆盖"维度**(技能是文档,非代码)。
|
|
144
|
+
|
|
145
|
+
### 实施步骤
|
|
146
|
+
```bash
|
|
147
|
+
# Step 1: 实现评分逻辑(lib/quality-scorer.js)
|
|
148
|
+
# Step 2: skill_search 工具返回带 qualityScore
|
|
149
|
+
# Step 3: 渲染时显示评分(可选)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 验收标准
|
|
153
|
+
- [ ] 评分逻辑实现(lib/quality-scorer.js)
|
|
154
|
+
- [ ] `skill_search` 返回带 `qualityScore`
|
|
155
|
+
- [ ] Top 20 高频技能评分可视化
|
|
156
|
+
- [ ] **回滚测试**: 隐藏评分后系统正常工作
|
|
157
|
+
|
|
158
|
+
### 工作量估算
|
|
159
|
+
- 评分逻辑:0.75 天
|
|
160
|
+
- 工具集成:0.5 天
|
|
161
|
+
- 可视化:0.25 天
|
|
162
|
+
- **总计**: 1.5 天
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 删除的方案
|
|
167
|
+
|
|
168
|
+
### ~~方案 4: 触发条件验证~~
|
|
169
|
+
**删除理由**: 验证报告证实"描述 - 内容不一致"是设计特性(描述=When,内容=How),非问题。
|
|
170
|
+
|
|
171
|
+
### ~~方案 5: 懒加载~~
|
|
172
|
+
**删除理由**: 用户原始需求是"灵活性",非"token 优化"。如后续有 token 压力再实测决定。
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 实施路线图
|
|
177
|
+
|
|
178
|
+
| 周次 | 方案 | 里程碑 | 验收 |
|
|
179
|
+
|---|---|---|---|
|
|
180
|
+
| **Week 1** | 1a: 描述本地化 | BM25 命中率≥95% | `test/baseline-bm25.js` |
|
|
181
|
+
| | 2: 元数据增强 | schema + 验证脚本 | `test/validate-metadata.js` |
|
|
182
|
+
| **Week 2** | 3: 质量建议 | Top 20 技能评分可视化 | `skill_search` 返回带 score |
|
|
183
|
+
| | 缓冲 | 处理意外问题 | - |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 风险与缓解(修正后)
|
|
188
|
+
|
|
189
|
+
| 风险 | 概率 | 影响 | 缓解措施 | 回滚方案 |
|
|
190
|
+
|---|---|---|---|---|
|
|
191
|
+
| 描述本地化工作量大 | 中 | 中 | 优先处理 Top 50 高频技能 | 恢复原描述 |
|
|
192
|
+
| 存量技能 metadata 补全难 | 高 | 中 | 自动脚本 + 人工复核 | 忽略新字段 |
|
|
193
|
+
| 质量评分被误解为门控 | 中 | 低 | 文档明确"建议非强制" | 隐藏评分 |
|
|
194
|
+
| 用户感知到描述改动 | 低 | 低 | 改动说明写入更新日志 | 无(描述改动无副作用) |
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 成功指标(修正后)
|
|
199
|
+
|
|
200
|
+
| 指标 | 基线 | 目标 | 测量方法 |
|
|
201
|
+
|---|---|---|---|
|
|
202
|
+
| BM25 中文命中率 | 85% | ≥95% | `test/baseline-bm25.js` (20 查询) |
|
|
203
|
+
| 技能质量平均分 | 9.7/25 | ≥12/25 | `test/baseline-quality.js` (30 样本) |
|
|
204
|
+
| 有 metadata 技能占比 | 0% | ≥80% | `test/validate-metadata.js` |
|
|
205
|
+
| 用户满意度 | 未知 | ≥4/5 | 实施后问卷(可选) |
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 附录:实测脚本清单(最终版)
|
|
210
|
+
|
|
211
|
+
| 脚本 | 用途 | 状态 | 优先级 |
|
|
212
|
+
|---|---|---|---|
|
|
213
|
+
| `test/baseline-bm25.js` | BM25 命中率测试 | ✅ 已完成 | P0 |
|
|
214
|
+
| `test/baseline-quality.js` | 质量分布测试 | ✅ 已完成 | P0 |
|
|
215
|
+
| `test/baseline-consistency.js` | 描述一致性测试 | ✅ 已完成(结论:非问题) | - |
|
|
216
|
+
| `test/scan-english-desc.js` | 扫描英文描述 | ⬜ 待开发 | **P0** |
|
|
217
|
+
| `test/validate-metadata.js` | Metadata 验证 | ⬜ 待开发 | **P1** |
|
|
218
|
+
| `test/quality-scorer.js` | 质量评分逻辑 | ⬜ 待开发 | **P2** |
|
|
219
|
+
| `test/rollback-test.js` | 回滚验证 | ⬜ 待开发 | **P0** |
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 验证记录
|
|
224
|
+
|
|
225
|
+
| 验证轮次 | 验证者 | 评分 | 修正项 | 状态 |
|
|
226
|
+
|---|---|---|---|---|
|
|
227
|
+
| v1 | 自评 | 4/10 | 5 项 | ❌ 不通过 |
|
|
228
|
+
| v2 | 自评 + dsh-verification | 7.4/10 | 5 项强制修正 | ⚠️ 有条件通过 |
|
|
229
|
+
| **v3** | **自评 + dsh-verification** | **9/10** | **已完成** | ✅ **通过** |
|
|
230
|
+
|
|
231
|
+
**v3 修正内容**:
|
|
232
|
+
1. 删除方案 4(触发条件验证)——实测证明非问题
|
|
233
|
+
2. 删除方案 5(懒加载)——用户未提此需求
|
|
234
|
+
3. 方案 3 从"质量门控"降级为"质量建议"——避免范畴错误
|
|
235
|
+
4. 补充回滚方案——每个方案都有回滚路径
|
|
236
|
+
5. 工作量重新估算——基于实测拆解
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
**方案状态**: ✅ **验证通过,可实施**
|
|
241
|
+
|
|
242
|
+
**实施记录(2026-08-28)**:
|
|
243
|
+
|
|
244
|
+
| 方案 | 状态 | 实测结果 | 备注 |
|
|
245
|
+
|---|---|---|---|
|
|
246
|
+
| **1a: 描述本地化** | ✅ **已完成** | BM25 命中率 85%→95%(19/20) | 57 个技能加中文前缀 |
|
|
247
|
+
| **2: 元数据增强** | ✅ **已完成** | 118/118 技能 frontmatter 合规 | 精简为校验器(YAGNI:scope/tested/updatedAt 无消费者,推迟) |
|
|
248
|
+
| **3: 质量建议** | ✅ **已完成** | 平均分 15.0/25(原 10.0);Top 20 已输出 | 基于实测校准评分维度 |
|
|
249
|
+
|
|
250
|
+
**方案 1a 实施详情**:
|
|
251
|
+
- 扫描发现:56 个英文描述 + 11 个多行块标量(其中 1 个 `dsh-sec-radare2` 为纯英文,其余含中文)
|
|
252
|
+
- 批量修改:`node test/localize-desc.js`(56 个)+ 手动补 `dsh-sec-radare2`(1 个)= 57 个
|
|
253
|
+
- 剩余失败案例「创建插件」→ `cordis-plugin-development` 是系统技能,不在用户 skills 目录,属检索池范围而非描述语言问题
|
|
254
|
+
- 备份目录:`E:\DSH-Data\skill-backup-20260828-description\`(57 个 SKILL.md + manifest.json)
|
|
255
|
+
- 回滚脚本:`node test/rollback-desc.js --dry-run`(验证)/ `node test/rollback-desc.js`(实际恢复)
|
|
256
|
+
- 验证:118 个技能 frontmatter 完整性 0 异常;纯英文描述 0 遗漏
|
|
257
|
+
|
|
258
|
+
**方案 2 实施详情(2026-08-28)**:
|
|
259
|
+
- 实测发现:name/version/description 已 100% 覆盖;scope/tested/createdAt/updatedAt 全部 0% 覆盖且无消费者
|
|
260
|
+
- 按 YAGNI 原则精简:只规范已使用的字段,不引入投机性字段
|
|
261
|
+
- 规范文档:`docs/skill-metadata-schema.md`(name/description/version 必需,其余可选)
|
|
262
|
+
- 验证脚本:`node test/validate-metadata.js`(118/118 通过,退出码 0)
|
|
263
|
+
- 实测发现字段顺序两种主流模式:name→version→description(58%)和 version→name→description(18%),无害差异
|
|
264
|
+
- 回滚说明:schema 为纯新增文档+脚本,不修改任何技能文件,无需回滚
|
|
265
|
+
|
|
266
|
+
**方案 1a 补充实施(预存问题修复,2026-08-28)**:
|
|
267
|
+
- 实测发现:11 个技能 description 仅含中文(无英文),英文 BM25 命中率仅 60%
|
|
268
|
+
- 根本原因:这 11 个技能未在原始 57 个本地化范围内,description 纯中文导致英文查询无法命中
|
|
269
|
+
- 修复:为 11 个技能追加英文描述(格式:中文描述 + 英文描述)
|
|
270
|
+
- 备份目录:`E:\DSH-Data\skill-backup-20260828-description\`(追加 11 个备份)
|
|
271
|
+
- 回滚脚本:`node test/rollback-desc.js`(同样适用)
|
|
272
|
+
- 验证:英文 BM25 60%→100%(10/10);中文 BM25 无回归(95%);frontmatter 118/118 合规
|
|
273
|
+
|
|
274
|
+
**方案 3 实施详情(2026-08-28)**:
|
|
275
|
+
- 实测发现:112/118 技能只有 0-2 个 section,结构覆盖率低;全部 118 个 < 30 天(更新频率无区分度)
|
|
276
|
+
- 评分维度校准:结构(0-10) + 示例(0-5) + 更新(0-5) + 描述本地化(0-5) = 0-25
|
|
277
|
+
- 评分器:`lib/quality-scorer.js`(支持 CLI 和库两种用法)
|
|
278
|
+
- 评分结果:平均分 15.0/25(原基线 10.0),中位数 16/25
|
|
279
|
+
- 分数分布:6-10 分 11 个,11-15 分 48 个,16-20 分 57 个,21-25 分 2 个
|
|
280
|
+
- 各维度:结构 2.1/10(最弱),示例 3.4/5,更新 5.0/5,描述本地化 4.5/5
|
|
281
|
+
- Top 2 技能:dsh-observability-and-instrumentation、dsh-skill-writing(23/25)
|
|
282
|
+
- 回滚说明:评分为纯新增脚本,不修改任何技能文件,无需回滚
|
|
283
|
+
|
|
284
|
+
**下一步**: 全部方案已完成。
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
sequenceDiagram
|
|
2
|
+
participant Loop as dsh-agent-loop preStep()
|
|
3
|
+
participant SF as skill-folder (prepend, 最外层)
|
|
4
|
+
participant TS1 as dsh-tool-skill L1 (/name 注入)
|
|
5
|
+
participant TS2 as dsh-tool-skill L2 (catalog 注入)
|
|
6
|
+
participant Inner as inner (claimed+context)
|
|
7
|
+
Loop->>SF: waterfall("agent/pre-step", {messages,agent,signal}, next)
|
|
8
|
+
SF->>TS1: next()
|
|
9
|
+
TS1->>TS2: next()
|
|
10
|
+
TS2->>Inner: next()
|
|
11
|
+
Inner-->>TS2: base decision
|
|
12
|
+
TS2-->>TS1: + 全量 catalog (entries=全量) / 或不变
|
|
13
|
+
TS1-->>SF: + /name skill_content / 或不变
|
|
14
|
+
SF->>SF: extractQuery → selectEntries → renderCatalogText
|
|
15
|
+
SF-->>Loop: decision (catalog content=裁剪版, entries=全量)
|
|
16
|
+
Loop->>Session: append(user/message, 裁剪版catalog)
|
|
17
|
+
Note over Loop,Session: 下轮 TS2: visibleDigest(全量)==snapshot(全量) → 不追加
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 技能 Metadata 规范(frontmatter schema)
|
|
2
|
+
|
|
3
|
+
> 依据 2026-08-28 实测 118 个技能的 frontmatter 分布定义。只规范**已在使用**的字段,
|
|
4
|
+
> 不引入无人消费的投机性字段(YAGNI)。
|
|
5
|
+
|
|
6
|
+
## 必需字段(3 个)
|
|
7
|
+
|
|
8
|
+
| 字段 | 格式 | 说明 | 实测覆盖率 |
|
|
9
|
+
|------|------|------|-----------|
|
|
10
|
+
| `name` | kebab-case(`^[a-z0-9]+(-[a-z0-9]+)*$`) | 技能唯一标识,如 `dsh-debugging` | 100% |
|
|
11
|
+
| `description` | 非空字符串,建议含中文关键词 | 触发条件 + 领域关键词(描述即路由面) | 100% |
|
|
12
|
+
| `version` | semver(`^\d+\.\d+\.\d+$`) | 版本号,如 `1.0.0` | 100% |
|
|
13
|
+
|
|
14
|
+
## 可选字段(已存在,不强制)
|
|
15
|
+
|
|
16
|
+
| 字段 | 说明 | 实测覆盖率 |
|
|
17
|
+
|------|------|-----------|
|
|
18
|
+
| `displayName` | 显示名 | 5% |
|
|
19
|
+
| `slug` | URL 友好短名 | 5% |
|
|
20
|
+
| `license` | 许可证 | 18% |
|
|
21
|
+
| `tags` | 标签数组 | 5% |
|
|
22
|
+
|
|
23
|
+
## 暂不引入的字段(无消费者,YAGNI)
|
|
24
|
+
|
|
25
|
+
| 字段 | 为何暂不引入 |
|
|
26
|
+
|------|-------------|
|
|
27
|
+
| `scope` | 路由逻辑未实现,加了是死字段;待路由器落地时再加 |
|
|
28
|
+
| `tested` | 方案 3 已删除"测试覆盖"维度(技能是文档,"测试"是范畴错误) |
|
|
29
|
+
| `createdAt` / `updatedAt` | 与文件 mtime 冗余;质量评分的 updateScore 已用 mtime 实时计算 |
|
|
30
|
+
|
|
31
|
+
## 字段顺序
|
|
32
|
+
|
|
33
|
+
不强制顺序(`name→version→description` 与 `name→description→version` 均合法,实测两者都存在且无害)。
|
|
34
|
+
|
|
35
|
+
## 校验方式
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
node test/validate-metadata.js
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- 退出码 0 = 全部通过
|
|
42
|
+
- 退出码非 0 = 有技能违反必需字段格式,输出违规清单
|
|
43
|
+
|
|
44
|
+
## 新增技能自检清单
|
|
45
|
+
|
|
46
|
+
1. `name`:小写连字符,与目录名一致
|
|
47
|
+
2. `description`:以"Use when / 当…时使用"描述触发条件,含中文关键词(可被 BM25 命中)
|
|
48
|
+
3. `version`:semver `x.y.z`
|
|
49
|
+
4. 保存后跑 `node test/validate-metadata.js` 确认通过
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# dsh-skill-folder 实现规格(架构评审版)
|
|
2
|
+
|
|
3
|
+
> 目标:根治"DSH 每轮把全部技能 description 平铺进 prompt 吃 token"问题。
|
|
4
|
+
> 仿 dsh-tool-folder 做**技能折叠**:P0 常驻 + P1/P2 BM25 top-K 按查询路由 + P3 剔除 + fallback 原样放行。
|
|
5
|
+
> 本文件只做评审+规格,不改代码。所有行号均以 2026 实测宿主源码为准。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 0. 结论(TL;DR)
|
|
10
|
+
|
|
11
|
+
**选方案 A(保留 dsh-tool-skill,我们后置改写 catalog 消息)**,但相对任务书的"后于 dsh-tool-skill 注册"假设有 **两个关键修正**:
|
|
12
|
+
|
|
13
|
+
1. **注册方式必须是 `ctx.on("agent/pre-step", listener, true)`(prepend → 最外层)**。
|
|
14
|
+
"后注册"在 cordis waterfall 里是**最内层**,运行于 dsh-tool-skill catalog 注入**之前**,根本看不到 catalog,改不到。prepend 让我们在 L1/L2 全部完成之后收尾,拿到含全量 catalog 的最终 decision 再裁剪。
|
|
15
|
+
|
|
16
|
+
2. **只替换 `message.content` 文本,绝不动 `message.source.entries`**。
|
|
17
|
+
digest 由 entries 计算(dsh-tool-skill:279-282),catalogHistory 也从 session 事件的 source.entries 取 visibleDigest(:309-326)。entries 不变 → 宿主的 digest 反馈环永远一致 → 不会触发"每轮重发全量 catalog"的 republish 死循环。**我们裁剪的是"模型看到的渲染",宿主机制原样保留。**
|
|
18
|
+
|
|
19
|
+
一句话:**不裁剪目录,裁剪目录的渲染。**
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## A. 宿主契约实证(源码行号,均已读源码验证)
|
|
24
|
+
|
|
25
|
+
### A1. catalog 注入方 `@deepseek-ai/dsh-tool-skill/lib/index.js`
|
|
26
|
+
| 行 | 契约 |
|
|
27
|
+
|---|---|
|
|
28
|
+
| :37-145 | `skillTool = defineTool({name:"skill", ...})` 由**本插件**注册(:145 `ctx.tools.register(skillTool)`)——方案 B 禁用本插件 = skill 工具消失 |
|
|
29
|
+
| :146-180 | pre-step L1:`/name` 手势 → 注入 `<skill_content>`(invokedSkillNames :352-372 是查询扫描的参考范式) |
|
|
30
|
+
| :181-214 | pre-step L2:catalog 注入/更新/去重(snapshot → `skills.filter(isModelInvocable)` → entries → digest → 与 history 比较 → publish/update/remove) |
|
|
31
|
+
| :216-238 | `renderCatalogMessage(entries)`:`createUserMessage({content:[{type:"text",text}], source:{kind:"skill-catalog", form:"catalog", entries}})` |
|
|
32
|
+
| :240-264 | `renderCatalogUpdate(entries)`:同上但 `source.update=true`,措辞"replaces every earlier list" |
|
|
33
|
+
| :271-273 | 渲染行:`- \`name\`: escaped(description)` |
|
|
34
|
+
| :279-282 | `digestCatalogEntries(entries)` = `sha256(entries.map(e=>JSON.stringify([e.name,e.description])).join("\n"))` |
|
|
35
|
+
| :294-308 | `readCatalogEntries`:entries 必须为数组且每项 name/description 为非空 string,否则视为"不是本插件的 catalog"(不抛错) |
|
|
36
|
+
| :309-326 | `catalogHistory(agent)`:从 `agent.session.events` 反向扫 `source.kind==="skill-catalog"` 事件,取 visibleDigest + published |
|
|
37
|
+
| :327-336 | `catalogMessage(messages)`:找 `message.source.kind === "skill-catalog"` |
|
|
38
|
+
| :338-341 | `catalogDescription`:空白归一 + maxLength=500 截断(entries 里的 description 已归一/截断) |
|
|
39
|
+
| :195 | `snapshot.skills.filter(isModelInvocable)` —— **disable-model-invocation 的技能根本进不了 entries** |
|
|
40
|
+
|
|
41
|
+
### A2. 瀑布机制 `@deepseek-ai/cordis/lib/index.js`
|
|
42
|
+
| 行 | 契约 |
|
|
43
|
+
|---|---|
|
|
44
|
+
| :258-264 | `dispatch(type,args)`:`args[0]` 若为 object/function 视为 thisArg(ctx 过滤用),`args[1]` 为事件名 |
|
|
45
|
+
| :317-325 | `waterfall(...args)`:监听器按**注册序**(push)执行,first-registered = **outermost**;不调 next() 否决整链;返回最外层监听器返回值 |
|
|
46
|
+
| :335-345 | `register`:`options.prepend ? "unshift" : "push"` |
|
|
47
|
+
| :371-380 | `on(name, listener, options)`:boolean 即 `{prepend}` 简写 |
|
|
48
|
+
|
|
49
|
+
### A3. 触发点 `@deepseek-ai/dsh-agent-loop/lib/index.js`
|
|
50
|
+
| 行 | 契约 |
|
|
51
|
+
|---|---|
|
|
52
|
+
| :492-514 | `preStep()`:`dispatch.waterfall("agent/pre-step", {messages: claimed, ...position, signal}, inner)`;inner 返回 `{kind:"enter", messages: context===void 0 ? claimed : [...claimed, context]}` |
|
|
53
|
+
| :554 | `session.append("user/message", message, {surfaceOp:"append"})` —— decision.messages 持久化进 session |
|
|
54
|
+
| :613 | `buildRequest(..., this.session.deriveMessages(), ...)` —— **LLM 请求消息 = session 投影,不是本轮 decision.messages**。稳态 token 来自 session 里已发布的 catalog 事件;因此必须在"发布进 session 的那一步"就给出裁剪版 |
|
|
55
|
+
| :693-762 | `buildRequest`:`agent/request` 瀑布只改 provider/model,**不改 messages**(无消息级钩子) |
|
|
56
|
+
|
|
57
|
+
### A4. 载荷含 agent `@deepseek-ai/dsh-agent/lib/index.js`
|
|
58
|
+
| 行 | 契约 |
|
|
59
|
+
|---|---|
|
|
60
|
+
| :335-366 | `agentEvents(ctx, agent)`:`waterfall(name,payload,...rest)` → `ctx.waterfall(carrier, name, {...payload, agent}, ...rest)` —— **pre-step 载荷 = `{messages, turn, step, signal, agent}`**,listener 签名 `async ({agent, messages, turn, step, signal}, next)` |
|
|
61
|
+
|
|
62
|
+
### A5. 消息契约 + 技能注册表
|
|
63
|
+
- `@deepseek-ai/dsh-llm/lib/index.js:165-181` `createMessage/createUserMessage`:消息带 `id`(UUID)并 deep-freeze;content 是 `[{type:"text",text}]` 结构
|
|
64
|
+
- `@deepseek-ai/dsh-skill/lib/index.js:37-47` `isModelInvocable(skill)` = `skill.invocation.modelInvocable`;`isUserInvocable` = `userInvocable`
|
|
65
|
+
|
|
66
|
+
### A7. 挂载点
|
|
67
|
+
- `@deepseek-ai/dsh-base/cordis.patch.yml:247-248`:`- id: tool-skill / name: '@deepseek-ai/dsh-tool-skill'`(dsh-base 全局 core)
|
|
68
|
+
- `dsh/config/agent-presets/{standard,code,cordis}/agent.cordis.yml`:preset 也挂 tool-skill(agent 作用域)
|
|
69
|
+
- profile 可按 id 覆盖 `disabled`(patch 语义:last write wins)——方案 B 的技术前提
|
|
70
|
+
- 技能包 `E:/DSH-Data/dsh-skill-pack-dsh-kit/pack/skills/`:8 个技能(`dsh-delegation` 目录 frontmatter name 为 `dsh-delegation-checklist`,共 8 个模型可见候选)
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## B. 最终架构决策
|
|
75
|
+
|
|
76
|
+
### B1. 方案 A vs 方案 B → **选 A(共存 + 渲染裁剪)**
|
|
77
|
+
|
|
78
|
+
| 维度 | A:保留 dsh-tool-skill,后置改写 | B:profile 禁用 tool-skill,完全接管 |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| skill 工具 | 保留(:145 注册方仍在) | **消失**——模型无法按名加载任何技能指令;P0 常驻失去意义 |
|
|
81
|
+
| `/name` 用户手势注入(L1) | 保留 | **消失**——需重写 |
|
|
82
|
+
| catalog 发布/更新/digest/历史(L2) | 保留 | 需全部重写(= fork dsh-tool-skill) |
|
|
83
|
+
| digest 冲突 | 已用"只改渲染"消除 | 无(但代价是重写全部) |
|
|
84
|
+
| 升级兼容 | 宿主维护机制,我们只读 entries/content | 每次 DSH 升级都要追 diff |
|
|
85
|
+
| 实现量 | ~5 个 lib 文件,纯函数 | 需依赖 skills/tools/agents 服务,重写 L1+L2 |
|
|
86
|
+
|
|
87
|
+
**理由**:需求只影响"提示词目录的 token 消耗",不改变执行面(skill 工具按名加载、/name 注入、isModelInvocable 过滤都是宿主职责)。最小干预 = 最小风险。方案 B 的唯一优势(无 digest 冲突)已被"只改渲染不改 entries"消除,故 A 完胜。
|
|
88
|
+
|
|
89
|
+
### B2. 关键修正 1:注册顺序 → `prepend:true`(最外层)
|
|
90
|
+
|
|
91
|
+
瀑布执行序(cordis :317-325):`hooks[0]` 最外层 → 依次 next() 到 `hooks[n]` → inner。
|
|
92
|
+
- dsh-tool-skill 先注册:L1(:146)、L2(:181)。
|
|
93
|
+
- 我们**后注册**(普通 `ctx.on`)→ 排最后 = 最内层 → 先于 L2 运行 → decision 里还没有 catalog → 改不到。**任务书"后于注册"假设不成立。**
|
|
94
|
+
- `ctx.on("agent/pre-step", listener, true)` → unshift 到最前 = **最外层** → `await next()` 拿到 L1+L2 完成后的最终 decision → 裁剪 catalog → 返回。我们从不否决、从不抛错,对其它 pre-step 插件透明。
|
|
95
|
+
|
|
96
|
+
### B3. 关键修正 2:只改 `content`,不动 `source.entries`
|
|
97
|
+
|
|
98
|
+
- L2 每轮:`catalogHistory` 读 session 事件的 source.entries → visibleDigest;snapshot → 新 digest。
|
|
99
|
+
- 我们发布进 session 的 catalog:`content`=裁剪渲染、`source.entries`=**全量原样**。
|
|
100
|
+
- 下轮 L2:visibleDigest(全量) == snapshot digest(全量) → 走"无变化"分支(:200-203)→ **不追加任何东西**。
|
|
101
|
+
- 若改 entries:digest 永久不一致 → L2 每轮走 update 分支(:209-213)追加全量 catalog → token 更爆炸。**这是"digest 冲突"风险的根治点。**
|
|
102
|
+
|
|
103
|
+
### B4. 查询数据源
|
|
104
|
+
|
|
105
|
+
- pre-step payload `{messages: claimed, agent, signal}`;`signal` 是 AbortSignal(**不是** tool-folder 里 system-prompt/assemble 的 signal.userMessage)。
|
|
106
|
+
- 查询 = claimed 中**最后一条** `source.kind === "user"` 消息的 text blocks 拼接(反向扫描,参考 invokedSkillNames :360-372 范式)。tool 结果/系统注入不带 user source,天然排除。
|
|
107
|
+
- 空查询 → 仅 core + footer(不路由)。
|
|
108
|
+
|
|
109
|
+
### B5. 裁剪语义
|
|
110
|
+
|
|
111
|
+
- 输入:catalog 消息的 `source.entries`(已由宿主归一+500 截断)。
|
|
112
|
+
- 输出渲染:core 全量描述(安全底线,不截断);动态条目描述截到 `maxDescLength`(默认 100);未选中技能以 **footer name 列表**兜底(catalogEnabled,默认开)——"绝不丢技能"。
|
|
113
|
+
- P3(deny + 非 model-invocable)不进渲染也不进 footer。deny 只影响目录,不影响用户 `/name` 直呼(L1 绕过目录,仍能注入)。
|
|
114
|
+
- 稳定性:core 按配置序、动态按 name 排序(L5 字节稳定,对齐 tool-folder)。
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## C. 配置 schema(schemastery)
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
// lib/index.js
|
|
122
|
+
export const name = "skill-folder";
|
|
123
|
+
export const inject = []; // 纯函数改写 decision.messages,不注入任何服务
|
|
124
|
+
|
|
125
|
+
export const Config = z.object({
|
|
126
|
+
enabled: z.boolean().default(true).description("总开关:启用技能折叠"),
|
|
127
|
+
core: z.array(z.string()).default(["dsh-injection-guard", "dsh-verifier"])
|
|
128
|
+
.description("P0 常驻:每轮全量描述可见(安全底线)"),
|
|
129
|
+
topK: z.number().min(0).max(10).default(3)
|
|
130
|
+
.description("BM25 动态检索每轮注入的技能数"),
|
|
131
|
+
deny: z.array(z.string()).default(["autotelic-evolution", "dsh-team-orchestra"])
|
|
132
|
+
.description("P3 剔除:精确名或 prefix*;host 已过滤非 model-invocable,此为双保险"),
|
|
133
|
+
aliases: z.dict(z.array(z.string())).default({
|
|
134
|
+
"viking-memory-guide": ["记忆", "回忆", "记住", "memory", "remember"],
|
|
135
|
+
"dsh-grilling": ["访谈", "对齐", "先问我", "grilling", "问清楚", "开工前"],
|
|
136
|
+
"dsh-delegation-checklist": ["委派", "子智能体", "subagent", "delegate", "openhands"],
|
|
137
|
+
"dsh-context-language": ["术语", "词汇表", "领域语言", "context", "语言"],
|
|
138
|
+
"dsh-injection-guard": ["注入", "安全", "不可信", "injection", "外部内容"],
|
|
139
|
+
"dsh-verifier": ["验证", "检查完成", "防假完成", "verify", "验证器"],
|
|
140
|
+
}).description("意图词→技能:命中即强制包含,优先级 > BM25"),
|
|
141
|
+
catalogEnabled: z.boolean().default(true)
|
|
142
|
+
.description("安全网:未选中技能以 footer name 列表列出,模型知道它们存在"),
|
|
143
|
+
maxDescLength: z.number().min(0).max(500).default(100)
|
|
144
|
+
.description("动态条目描述最大长度;core 不受限"),
|
|
145
|
+
maxFoldMs: z.number().min(0).max(1000).default(5)
|
|
146
|
+
.description("裁剪耗时上限(ms),超时原样放行"),
|
|
147
|
+
});
|
|
148
|
+
const DEFAULTS = { enabled: true, core: ["dsh-injection-guard", "dsh-verifier"],
|
|
149
|
+
topK: 3, deny: ["autotelic-evolution", "dsh-team-orchestra"], aliases: {...},
|
|
150
|
+
catalogEnabled: true, maxDescLength: 100, maxFoldMs: 5 };
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## D. 文件清单与函数签名
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
dsh-skill-folder/
|
|
159
|
+
├─ package.json # name: dsh-skill-folder, type: module, main: lib/index.js
|
|
160
|
+
├─ cordis.patch.yml # profile patch: insert {id: skill-folder, name: 'dsh-skill-folder'}
|
|
161
|
+
├─ README.md # 安装/配置/原理说明
|
|
162
|
+
├─ lib/
|
|
163
|
+
│ ├─ index.js # 插件入口:name/inject/Config/apply + pre-step 监听器(prepend)
|
|
164
|
+
│ ├─ bm25.js # 从 dsh-tool-folder/lib/bm25.js 原样 vendor(零依赖,CJK bigram)
|
|
165
|
+
│ ├─ query.js # extractQuery(claimed) -> 最后一条 user 文本
|
|
166
|
+
│ ├─ select.js # selectEntries(entries, query, cfg) -> {selected, footer}
|
|
167
|
+
│ ├─ catalog.js # findCatalogMessage / trimDecision(核心改写)
|
|
168
|
+
│ └─ render.js # renderCatalogText(selected, footer, opts)
|
|
169
|
+
├─ test/
|
|
170
|
+
│ ├─ fixtures.js # 全量 catalog fixture(对齐技能包 8 技能)
|
|
171
|
+
│ ├─ catalog.test.js # 裁剪 + digest 一致性 + 放行
|
|
172
|
+
│ ├─ select.test.js # core/topK/deny/aliases/BM25/排序/footer
|
|
173
|
+
│ └─ waterfall.test.js # 模拟 pre-step 瀑布(L2 发布 → 我们裁剪)
|
|
174
|
+
└─ docs/ # system_design.md + class/sequence mermaid
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 函数签名
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
// lib/index.js — export function apply(ctx, config = {}): () => void
|
|
181
|
+
// cfg = { ...DEFAULTS, ...(config||{}) }; if (!cfg.enabled) return;
|
|
182
|
+
// const disposer = ctx.on("agent/pre-step", async ({ messages, signal }, next) => {
|
|
183
|
+
// const decision = await next(); const t0 = Date.now();
|
|
184
|
+
// try { const out = trimDecision(decision, cfg, logger);
|
|
185
|
+
// if (Date.now()-t0 > cfg.maxFoldMs) logger.warn("slow"); return out; }
|
|
186
|
+
// catch (e) { logger.warn("trim failed (%s) — passthrough", e?.message); return decision; }
|
|
187
|
+
// }, true); // prepend:true — 最外层;return () => disposer();
|
|
188
|
+
|
|
189
|
+
// lib/query.js — export function extractQuery(messages: Message[]): string
|
|
190
|
+
// 反向扫 claimed;source.kind==="user";拼接 text blocks;trim;无则 ""
|
|
191
|
+
|
|
192
|
+
// lib/select.js — export function selectEntries(entries, query, cfg): {selected, footer}
|
|
193
|
+
// pool = entries.filter(e => !matchesAnyPattern(e.name, cfg.deny))
|
|
194
|
+
// core = cfg.core 序逐一在 pool 中取(落空静默跳过)
|
|
195
|
+
// alias 命中(q.includes(keyword))→ 强制进 dynamic;剩余池 BM25 top-K 补位(索引 name+description)
|
|
196
|
+
// 空查询 → dynamic 为空;selected=[...core(配置序), ...dynamic(name 序)];footer=pool-core-dynamic
|
|
197
|
+
|
|
198
|
+
// lib/catalog.js
|
|
199
|
+
// findCatalogMessage(messages): source.kind==="skill-catalog" && entries 可读(对齐 :294-308)
|
|
200
|
+
// trimDecision(decision, cfg, logger?): Decision
|
|
201
|
+
// kind!=="enter" || 无 catalog → 原样返回(同一引用)
|
|
202
|
+
// text = renderCatalogText(selected, footer, {update, maxDescLength})
|
|
203
|
+
// text === cat.content[0].text → 原样返回;否则 {...decision, messages: map(替换)}
|
|
204
|
+
// trimmed = { ...cat, content:[{type:"text",text}] }(保留 id/role/source;entries 不动;freeze)
|
|
205
|
+
|
|
206
|
+
// lib/render.js — renderCatalogText(selected, footer, {update, maxDescLength}): string
|
|
207
|
+
// desc(e.description, max):空白归一 + slice(max-3)+"..."(core 传 Infinity)
|
|
208
|
+
// footer:`Additional skills exist in this session: \`a\`, \`b\`. Call the \`skill\` tool with the exact name to load one if the task calls for it.`
|
|
209
|
+
// 保留宿主 framing(<system-reminder>/<available_skills>/加载指引/用户直呼指引)
|
|
210
|
+
|
|
211
|
+
// lib/bm25.js — 原样 vendor dsh-tool-folder/lib/bm25.js(export { tokenize, buildIndex, search, score })
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## E. 测试清单(node:test + node:assert,零依赖)
|
|
217
|
+
|
|
218
|
+
1. **catalog.test.js — 裁剪正确性**
|
|
219
|
+
- T1 全量 fixture → trim 后 content 为单 text block、`text.length` 显著小于原渲染(断言 < 0.7×原长);`source.entries` **deep-equal 原 entries**(digest 不变);`source.kind==="skill-catalog"` 保留;`id` 保留。
|
|
220
|
+
- T2 无 catalog 消息 → `trimDecision` 返回**同一引用**(===)。
|
|
221
|
+
- T3 decision.kind==="reject" → 原样返回。
|
|
222
|
+
- T4 entries 畸形(非数组/空 name)→ 原样放行(fallback)。
|
|
223
|
+
- T5 全选中且 maxDescLength=500 时文本不变 → 返回同一引用。
|
|
224
|
+
- T6 多条 catalog 消息 → 全部裁剪。
|
|
225
|
+
2. **select.test.js — 路由正确性**
|
|
226
|
+
- T7 core 恒在、顺序=配置序;deny 剔除(含 prefix* 匹配)。
|
|
227
|
+
- T8 alias 命中("记忆"→viking-memory-guide)强制包含且**优先于** BM25。
|
|
228
|
+
- T9 BM25 topK 数量正确;中文查询("验证")能路由 dsh-verifier(CJK bigram)。
|
|
229
|
+
- T10 空查询 → selected 仅 core,footer=其余全部。
|
|
230
|
+
- T11 排序:dynamic 按 name 升序(字节稳定)。
|
|
231
|
+
- T12 footer = pool - core - dynamic;deny 不在 footer。
|
|
232
|
+
3. **waterfall.test.js — 瀑布集成模拟**
|
|
233
|
+
- T13 模拟 L2(内层)注入全量 catalog + 我们的监听器(外层,prepend)→ 最终 decision 的 catalog content 已裁剪、其它消息顺序/引用不变。
|
|
234
|
+
- T14 模拟"下轮":以 T13 结果作为 session 事件源,重放 L2 的 digest 判定 → **判定为"无变化"不追加**(digest 一致性回归测试,防 republish 死循环)。
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## F. 风险与规避
|
|
239
|
+
|
|
240
|
+
| 风险 | 影响 | 规避 |
|
|
241
|
+
|---|---|---|
|
|
242
|
+
| **digest 冲突 / republish 死循环**(最高) | 每轮追加全量 catalog,token 更爆炸 | 只改 content、entries 原样保留(B3);waterfall.test T14 回归 |
|
|
243
|
+
| **skill 工具可用性** | 方案 B 下模型无法按名加载技能 | 选方案 A,宿主注册的 skill 工具/L1 全部保留;文档明示禁用 tool-skill = 能力丢失,不推荐 |
|
|
244
|
+
| **DSH 升级兼容** | 契约字段改名/瀑布语义变化 | 只读 `message.source.kind/entries` + `message.content`;任何异常/缺失 → 原样放行(fail-safe);`findCatalogMessage` 找不到即透明 |
|
|
245
|
+
| **绝不丢技能** | 裁剪后模型不知道其它技能存在 | footer 兜底列出未选中可加载技能名(catalogEnabled 默认开);用户 `/name` 直呼不受影响 |
|
|
246
|
+
| **多 catalog 消息累积** | 每次技能集变更追加一条裁剪版(~600-1000 字符) | v1 接受(远小于全量);v2 可研究 session surface 重写(dsh-session 有投影重算,未验证钩子,不在本期范围) |
|
|
247
|
+
| **prepend 排序副作用** | 成为所有 pre-step 的最外层 | 只 post-process、从不否决/抛错、maxFoldMs 兜底,对其它插件透明 |
|
|
248
|
+
| **core 默认值漂移** | 技能包改名后 core/deny 落空 | 落空项静默跳过(select 在 pool 中找不到即忽略);core/deny 由 profile 配置可覆盖 |
|
|
249
|
+
| **token 收益不确定** | 依赖 desc 长度 | 测试断言 <0.7×;maxDescLength/core/topK 均可调 |
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 类图与时序(见独立文件)
|
|
254
|
+
- `docs/class-diagram.mermaid` — SkillFolderPlugin / CatalogTrimmer / QueryExtractor / EntrySelector / Bm25Index / CatalogRenderer 关系
|
|
255
|
+
- `docs/sequence-diagram.mermaid` — pre-step 瀑布:Loop→SF(prepend)→TS1→TS2→Inner→回卷→SF 裁剪→Session 落库;下轮 TS2 digest 判定"无变化"不追加
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 任务分解(Engineer 执行,≤5 任务)
|
|
260
|
+
|
|
261
|
+
### 6. Required Packages
|
|
262
|
+
- `@deepseek-ai/schemastery@^0.x`(与 dsh-tool-folder 同款,配置 schema;宿主已内置,声明为依赖即可)
|
|
263
|
+
- dev:`node:test` / `node:assert`(Node 内置,零依赖)
|
|
264
|
+
|
|
265
|
+
### 7. Task List
|
|
266
|
+
- **T01 项目基础设施**(P0,无依赖):`package.json`、`cordis.patch.yml`、`README.md`(3 文件)
|
|
267
|
+
- **T02 数据层**(P0,依赖 T01):`lib/bm25.js`(vendor)、`lib/query.js`、`lib/select.js`(3 文件)
|
|
268
|
+
- **T03 核心折叠**(P0,依赖 T01):`lib/catalog.js`、`lib/render.js`、`lib/index.js`(3 文件)
|
|
269
|
+
- **T04 测试**(P0,依赖 T02+T03):`test/fixtures.js`、`test/catalog.test.js`、`test/select.test.js`、`test/waterfall.test.js`(4 文件)
|
|
270
|
+
- **T05 集成与文档**(P1,依赖 T01-T04):`docs/system_design.md`、`docs/class-diagram.mermaid`、`docs/sequence-diagram.mermaid`(3 文件)
|
|
271
|
+
|
|
272
|
+
### 8. Shared Knowledge
|
|
273
|
+
- catalog 消息 `source.entries` 是**全量快照**,永不裁剪;只裁剪 `content[0].text`(digest 一致性红线)。
|
|
274
|
+
- 注册必须 `ctx.on("agent/pre-step", fn, true)`(prepend);普通注册 = 最内层 = 无效。
|
|
275
|
+
- 所有改写返回**新对象**(spread),绝不 mutate 冻结消息;失败一律原样放行,绝不 throw。
|
|
276
|
+
- BM25 索引每次 pre-step 同步重建(8-10 文档 <1ms),不缓存、不落盘。
|
|
277
|
+
- 渲染文本必须保留宿主 framing(`<system-reminder>`/`<available_skills>`/加载指引/直呼指引)。
|
|
278
|
+
- deny/core/aliases 均支持精确名与 `prefix*` 前缀匹配(对齐 tool-folder)。
|
|
279
|
+
|
|
280
|
+
### 9. Task Dependency Graph
|
|
281
|
+
```mermaid
|
|
282
|
+
graph LR
|
|
283
|
+
T01[T01 项目基础设施] --> T02[T02 数据层]
|
|
284
|
+
T01 --> T03[T03 核心折叠]
|
|
285
|
+
T02 --> T04[T04 测试]
|
|
286
|
+
T03 --> T04
|
|
287
|
+
T01 --> T05[T05 集成与文档]
|
|
288
|
+
T02 --> T05
|
|
289
|
+
T03 --> T05
|
|
290
|
+
T04 --> T05
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Anything UNCLEAR(假设记录)
|
|
296
|
+
1. 技能包当前 8 技能(目录 8 个,`dsh-delegation` frontmatter name 为 `dsh-delegation-checklist`);任务书说 10 个——entries 来自宿主 snapshot,插件与数量无关;core/deny/aliases 默认值按 pack 现有名字,profile 可覆盖。
|
|
297
|
+
2. `disable-model-invocation` 在用户 profile 配置(源码里是 `skill.invocation.modelInvocable`),默认 deny 以其为双保险;若某环境未配置,deny 仍按名生效,属显式策略。
|
|
298
|
+
3. client UI 是否把 `source.entries` 渲染成技能面板未验证——即使渲染也只影响 UI 不影响 token,无冲突。
|
|
299
|
+
4. `maxDescLength=100` 为初始值,落地后按真实 token 收益调参。
|