dsh-rule-engine 0.6.2 → 0.6.4
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 +178 -7
- package/lib/core/audit.js +21 -4
- package/lib/core/authorization.js +30 -33
- package/lib/core/config.js +4 -1
- package/lib/core/dualtrack-markers.js +61 -0
- package/lib/core/guard-core.js +151 -34
- package/lib/core/intent.js +15 -22
- package/lib/core/lang.js +61 -0
- package/lib/core/lexicon.js +177 -72
- package/lib/core/llm-intent.js +2 -2
- package/lib/core/matcher.js +29 -17
- package/lib/core/measure-kinds.js +127 -0
- package/lib/core/patterns.js +382 -51
- package/lib/core/quality-ledger.js +124 -0
- package/lib/core/state.js +47 -4
- package/lib/core/text-detect.js +126 -68
- package/lib/core/tool-catalog.js +11 -3
- package/lib/core/understander.js +32 -43
- package/lib/index.js +133 -11
- package/lib/messages.js +134 -0
- package/package.json +3 -2
- package/scripts/check-tool-coverage.mjs +3 -1
- package/scripts/dualtrack-check.mjs +481 -0
- package/scripts/dualtrack-whitelist.json +7 -0
- package/scripts/local-residue-scan.mjs +10 -4
- package/scripts/plugins.json +10 -0
- package/scripts/publish-aptitude-check.mjs +5 -2
- package/scripts/release-plugin.mjs +54 -5
- package/scripts/verify-all.mjs +4 -0
- package/scripts/verify-v474.mjs +8 -8
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# dsh-rule-engine
|
|
2
2
|
|
|
3
3
|

|
|
4
|
-

|
|
5
5
|
|
|
6
|
-
DSH 规则执行引擎 v3 的插件实现。它把 `~/.dsh/AGENTS.md` 当作唯一真相源,自动解析规则四要素与执行等级,再通过「工具守卫 + 文本检测 + 时序检查 +
|
|
6
|
+
DSH 规则执行引擎 v3 的插件实现。它把 `~/.dsh/AGENTS.md` 当作唯一真相源,自动解析规则四要素与执行等级,再通过「工具守卫 + 文本检测 + 时序检查 + 审计台账」执行用户规则,而不是内置一套与用户无关的安全清单。**当前版本 0.6.4**(**适用环境:DSH `0.1.5-rc.2`**——本机实测:装配成功 + 冷加载探针 PASS + `npm test` ALL TESTS PASSED + `npm run verify` 13 层全绿;兼容声明面见 `package.json` 的 `dshCompat`:`>=0.1.0-rc.3 <0.2.0`,覆盖 0.1.5 线)。本版三项修复:**D2**(待决 ask 提升为会话级——修复"用户消息与 ask 答复并发"致授权全部未登记)+ **B1/C1**(规则 24 官方 bundle 豁免兑现;装配不一致的收敛豁免)+ **A2**(ask 授权登记改复数路径、`authMatches` 支持多路径匹配)。
|
|
7
7
|
|
|
8
|
-
> 当前版本 **0.6.
|
|
8
|
+
> 当前版本 **0.6.3**(0.1.2-rc.1 适配:Remote 新合同(typert-protocol `Remote(undefined, …)` 形态,typeof-null 陷阱修复)+ peer 锚扩 `>=0.1.2-rc.0`;白名单 3 轮扩充(gh 只读态 / `gh api` 只读 / `git ls-remote` / `cmdkey /list` + Do- 结构词回归修复);E7 豁免预插(release-plugin bump 后自动追加 pnpm minimumReleaseAgeExclude——⑬ 口径防发布后红灯窗口,单源 `scripts/lib/pnpm-exempt.mjs`))。本插件面向"规则机器化执行":规则写在 AGENTS.md 里,引擎负责让它们真的被遵守;所有规则动态解析,规则增删改后无需重写插件。
|
|
9
|
+
>
|
|
10
|
+
> **0.6.4(最新;取代上行版本号)**——**适用环境验证:DSH `0.1.5-rc.2`**(本机实测:装配成功 + 冷加载探针 PASS + `npm test` ALL TESTS PASSED + `npm run verify` 13 层全绿;兼容声明面见 `package.json` 的 `dshCompat`:`>=0.1.0-rc.3 <0.2.0`,覆盖 0.1.5 线)。本版三项修复:**D2**(待决 ask 提升为会话级——修复"用户消息与 ask 答复并发"致授权全部未登记)+ **B1/C1**(规则 24 官方 bundle 豁免兑现;装配不一致的收敛豁免——守卫不再拦下唯一能收敛的动作)+ **A2**(ask 授权登记改复数路径、`authMatches` 支持多路径匹配——修复"多路径只取一个、取最长取到次要路径")。
|
|
9
11
|
|
|
10
12
|
## 项目背景
|
|
11
13
|
|
|
@@ -23,7 +25,7 @@ DSH 规则执行引擎 v3 的插件实现。它把 `~/.dsh/AGENTS.md` 当作唯
|
|
|
23
25
|
本插件定位:**通用规则执行引擎**——任何用户的 AGENTS.md 规则集均可使用;本机工作流偏好仅作默认兜底(配置层),不含任何强制绑定。
|
|
24
26
|
|
|
25
27
|
- **声明式绑定**:规则正文可写 `<!-- handler: xxx -->`(或经插件配置 `handlerOverrides: {规则编号: 执行器名}`)显式绑定引擎执行器;未声明的规则按"纯自证"处理(参与匹配/自证提示,不参与机器硬拦);
|
|
26
|
-
-
|
|
28
|
+
- **措施类型(kind)**:声明/配置写 kind 名(`approval`/`backup`/`inline-command`…)即绑定对应执行器;历史内部名(`rule12a-approval` 等)经兼容别名表归一为同一 kind,**旧配置与旧规则正文无需改动**;
|
|
27
29
|
- **禁用语义**:管理器禁用的规则以"禁用占位"存在——`/guard rules` 显示"(已禁用)",不参与硬拦/纠察;恢复启用后自动重新生效(禁用 ≠ 删除,不再"消失");
|
|
28
30
|
- **无 AGENTS.md 也可用**:无规则文件时引擎零错加载、零规则、零误拦(发布门禁验证);
|
|
29
31
|
- **本机偏好表下沉配置**:内置默认偏好表已从代码移除——通用部署=空表(代码零本机编号);本机偏好经 `rule-engine.json` 的 `handlerDefaultMap` 提供(可整体替换/清空,引擎升级不丢);
|
|
@@ -70,6 +72,151 @@ DSH 规则执行引擎 v3 的插件实现。它把 `~/.dsh/AGENTS.md` 当作唯
|
|
|
70
72
|
|
|
71
73
|
**迁移(0.5.x → 0.6.0)**:0.6.0 前默认生效的守卫在升级后**不再默认激活**——需要本机行为(统一入口保护 / 手册豁免 / M8 记忆链 / 追加受保护文件)时,按上表把配置段并入本机 `rule-engine.json`。**通用用户无需任何配置**;未配置时相关守卫路径不存在,直写任意文件不受引擎限制(这是 0.6.0 的设计决定:发布物无权限制其他用户的写入方式;本机约定属于本机配置,不属于通用引擎)。
|
|
72
74
|
|
|
75
|
+
## 词表配置(lexicons / patterns / criticismPersonal / dualtrack)
|
|
76
|
+
|
|
77
|
+
引擎的行为词表与检测正则**全部可配置**:代码里只留机制与语言无关的内置默认,中文/本机词表放在 `rule-engine.json`。
|
|
78
|
+
|
|
79
|
+
### 分界:通用层 vs 个人层
|
|
80
|
+
|
|
81
|
+
| 层 | 在哪 | 内容 | 谁维护 |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| **通用层** | `lib/` 代码(随包发布) | 机制 + 语言无关最小集(英文核心词) | 插件作者 |
|
|
84
|
+
| **个人层** | `rule-engine.json` | 你的语言/习惯词表(如中文) | 你 |
|
|
85
|
+
|
|
86
|
+
**Override 语义**(三个键一致):
|
|
87
|
+
|
|
88
|
+
- 键**缺省**(不写)→ 用内置默认(通用最小集);
|
|
89
|
+
- 键**存在** → 按键**完全替换**(不做合并,配置即真相);
|
|
90
|
+
- 写 `{}` → 回退内置默认;**删键** → 同样回退。
|
|
91
|
+
|
|
92
|
+
> 配置在插件启动时读取。改完保存后**重载插件**(或重启 DSH)生效。
|
|
93
|
+
|
|
94
|
+
### 三个配置键
|
|
95
|
+
|
|
96
|
+
#### 1. `lexicons` —— 行为词表(11 键)
|
|
97
|
+
|
|
98
|
+
判定“用户这句话是什么意思”用的词表:
|
|
99
|
+
|
|
100
|
+
| 键 | 作用 |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `approval` | 执行许可词(确认/同意/可以…) |
|
|
103
|
+
| `exec_follow` | 执行语(即可/现在/马上…) |
|
|
104
|
+
| `approval_exec` | 派生:许可词 + 16 字内 + 执行语(可显式覆盖) |
|
|
105
|
+
| `plan_only` | 纯方案/理解确认(不算授权) |
|
|
106
|
+
| `action_words` | 执行动作词(意图判定与授权判定共用) |
|
|
107
|
+
| `strong_exec` | 强执行语(直接执行指令) |
|
|
108
|
+
| `directive` | 指令式动作词 |
|
|
109
|
+
| `rejection` | 拒绝词 |
|
|
110
|
+
| `question` | 疑问词 |
|
|
111
|
+
| `status_signal` | 状态信号词(“我已重启”) |
|
|
112
|
+
| `dangerous_action` | 危险动作词(命中则不算状态信号) |
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"lexicons": {
|
|
117
|
+
"action_words": "执行|跑|落盘|重构|部署",
|
|
118
|
+
"question": "[??]|吗|为什么|怎么"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### 2. `patterns` —— 检测正则(正则键 + 映射键 + 数值键)
|
|
124
|
+
|
|
125
|
+
文本健康检测(规则 2 时间 / 5 来源 / 7 承诺 / 11 术语 / 16 建议 / 19 / 21 / 22 / 23 / 27 / 31)与规则激活词。
|
|
126
|
+
|
|
127
|
+
- **正则键**:值是正则 source 字符串,如 `"time_words": "今天|昨天|刚才"`;
|
|
128
|
+
- **映射键** `self_cert_hints`:值是对象 `{ "14": "总结|汇报", "31": "撞墙|盲试" }`(键=规则编号);
|
|
129
|
+
- **数值键** `criticism_caps_ratio`:0–1 的阈值(英文全大写比率,默认 0.6)。
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"patterns": {
|
|
134
|
+
"time_words": "今天|昨天|刚才",
|
|
135
|
+
"promise_words": "包在我身上|肯定能|万无一失",
|
|
136
|
+
"self_cert_hints": { "14": "总结|汇报|完成" },
|
|
137
|
+
"criticism_caps_ratio": 0.6
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### 3. `criticismPersonal` —— 本机辱骂词枚举(数组)
|
|
143
|
+
|
|
144
|
+
批评检测的**个人词表**(通用层只留语言无关形态:连续问号/叹号、全大写比率)。
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{ "criticismPersonal": ["示例词一", "示例词二"] }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### 4. `dualtrack` —— 分层残留词表(**发布者私有**)
|
|
151
|
+
|
|
152
|
+
它不是行为词表,而是**发布门禁**用的「我这台机器长什么样」清单:`dualtrack-check`(B3 层)与 `local-residue-scan`(B2 层)用它扫 `lib/`,看有没有夹带本机信息(用户名、本机路径、私人概念等)。
|
|
153
|
+
|
|
154
|
+
| 键 | 作用 |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `markers` | 本机标识词表(字符串数组,每项一个待扫串) |
|
|
157
|
+
| `whitelist` | 本机豁免(`files` 整文件 / `strings` 精确串)——与包内 `scripts/dualtrack-whitelist.json` 合并生效 |
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"dualtrack": {
|
|
162
|
+
"markers": ["<你的用户名>", "<你的本机路径片段>"],
|
|
163
|
+
"whitelist": { "files": [], "strings": [] }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**加载优先级**(取先命中者):`rule-engine.json` 的 `dualtrack.markers` → 环境变量 `DUALTRACK_MARKERS` 指向的文件(每行一个,`#` 开头为注释)→ 包内 `scripts/local-residue-markers.txt`(仅示例)。
|
|
169
|
+
|
|
170
|
+
> **三者皆空 = REFUSED**(fail-closed):空词表不会被当成「没有残留」,而是直接拒绝执行——**空 txt 文件同样不作为词表源**。闸本身怎么跑,见「开发与测试」章节的「分层残留闸(dualtrack)」。
|
|
171
|
+
|
|
172
|
+
### 「加一个词」操作路径
|
|
173
|
+
|
|
174
|
+
1. 打开 `~/.dsh/rule-engine.json`(或你 DSH_HOME 下的同名文件);
|
|
175
|
+
2. 在 `lexicons` / `patterns` 里找到对应键,用 `|` 追加词(正则元字符需转义,如 `\\.`);
|
|
176
|
+
3. 保存 → 重载插件(或重启 DSH);
|
|
177
|
+
4. 想回滚:删掉该键(或整个键值)→ 恢复内置默认。
|
|
178
|
+
|
|
179
|
+
> **验证**:`/guard status` 查看配置加载是否正常;非法正则/未知键会在启动时记审计(`/guard log` 可查),**不会静默半套生效**。
|
|
180
|
+
|
|
181
|
+
## 质量账本(可选,默认关)
|
|
182
|
+
|
|
183
|
+
给**每个安装者**的私有质量统计:同类任务做得多了,能看出返工率是升是降。
|
|
184
|
+
|
|
185
|
+
### 是什么
|
|
186
|
+
|
|
187
|
+
| 层 | 内容 | 归属 |
|
|
188
|
+
|---|---|---|
|
|
189
|
+
| 机制 | `lib/core/quality-ledger.js`(`taskSignature` / `recordQuality` / `qualityTrend`) | 随包发布 |
|
|
190
|
+
| 数据 | `~/.dsh/quality-ledger.jsonl`(每单一行) | **本机数据,永不发布** |
|
|
191
|
+
| 配置 | `rule-engine.json` 的 `qualityLedger` 键 | 你的配置 |
|
|
192
|
+
|
|
193
|
+
### 怎么开
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{ "qualityLedger": { "enabled": true, "window": 5 } }
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
- `enabled` **默认 false**——不开**不会产生任何文件**;
|
|
200
|
+
- `window`:趋势比较窗口(最近 N 单 vs 之前 N 单,默认 5)。
|
|
201
|
+
|
|
202
|
+
### 怎么看
|
|
203
|
+
|
|
204
|
+
按签名查询趋势(同签名 = 同类任务):
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
import { taskSignature, qualityTrend } from "dsh-rule-engine/lib/core/quality-ledger.js";
|
|
208
|
+
const sig = taskSignature("给插件加一个设置项", ["npm test 全绿"]);
|
|
209
|
+
console.log(qualityTrend(sig).summary); // 方向:rework 改善 / 持平 / 恶化
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
每单记录的指标:`rework`(返工数)、`interventions`(介入数)、`frictions`(摩擦数=引擎拦截次数)、`tokens`。
|
|
213
|
+
|
|
214
|
+
### 隐私说明
|
|
215
|
+
|
|
216
|
+
**落盘的是单向指纹,不是原文。** 签名 = sha256(归一化内容) 取前 12 位;归一化规则(去引号 → 绝对路径替换为 `<path>` → 数字替换为 `<n>` → 折叠空白 → 小写)在机制层公开可审计。即使 `quality-ledger.jsonl` 被人看到,也只能知道「有个 12 位指纹的同类任务做过 N 次、返工趋势如何」,**看不出任务内容**。
|
|
217
|
+
|
|
218
|
+
> 本机制**不拦截、不评分、不上传**——纯旁路统计。
|
|
219
|
+
|
|
73
220
|
## 注入噪音治理(0.5.6 / 0.5.7)
|
|
74
221
|
|
|
75
222
|
只提醒真正值得提醒的事——这条原则贯穿 0.5.6 与 0.5.7:
|
|
@@ -96,7 +243,10 @@ DSH 规则执行引擎 v3 的插件实现。它把 `~/.dsh/AGENTS.md` 当作唯
|
|
|
96
243
|
> 更早版本(0.1.0-0.5.5)与本机历史要点见 git 历史;各版本内部"用户定稿"等决策细节不再随发布物携带。
|
|
97
244
|
|
|
98
245
|
| 版本 | 日期 | 要点 |
|
|
99
|
-
|
|
246
|
+
|---|---|---|
|
|
247
|
+
| **0.6.4** | 2026-09-10 | **本版**:**D2**(待决 ask 提升为会话级——修复"用户消息与 ask 答复并发"致授权全部未登记)+ **B1/C1**(规则 24 官方 bundle 豁免兑现;装配不一致的收敛豁免——守卫不再拦下唯一能收敛的动作)+ **A2**(ask 授权登记改复数路径、`authMatches` 支持多路径匹配)。**适用环境验证 = DSH `0.1.5-rc.2`**(装配 + 冷加载探针 + `npm test` + `verify` 13 层全绿) |
|
|
248
|
+
| **0.6.3** | 2026-09-09 | docs(readme): 词表配置章节补齐 dualtrack(markers/whitelist/加载优先级/空表 REFUSED) |
|
|
249
|
+
| **0.6.2** | 2026-09-08 | fix(0.6.2): whitelist 3-round + 0.1.2 adaptation + peer-anchor 0.1.2 + E7 pre-plug wiring |
|
|
100
250
|
|---|---|---|
|
|
101
251
|
| **0.6.1+(本地增强,未发版)** | 2026-09-07 | 只读豁免清单扩充(用户定调"纯只读顺畅"):`gh api` 只读态(无 -X/--method/graphql/-F/-f 的段)、`gh release/issue/pr/run view`、`cmdkey /list`、`git ls-remote` 显式;写形态(gh api -F/graphql、git fetch、curl 下载、重定向落盘)保持拦截;26 用例全绿 + 语法体检 + 热重载生效(发版需 bump 0.6.2) |
|
|
102
252
|
| **0.6.1** | 2026-09-07 | **B 档发布**:豁免预插(release-plugin bump 后自动追加 minimumReleaseAgeExclude——⑬ 口径防发布后红灯窗口);豁免判定单源化(scripts/lib/pnpm-exempt.mjs 与 verify-all ⑬ 共享 + 4 单测);viewFails 发布语境 STRICT 计 ❌;⑬ 头注释绝对口径 |
|
|
@@ -116,8 +266,11 @@ DSH 规则执行引擎 v3 的插件实现。它把 `~/.dsh/AGENTS.md` 当作唯
|
|
|
116
266
|
|
|
117
267
|
## 发行固定源
|
|
118
268
|
|
|
119
|
-
|
|
120
|
-
|
|
269
|
+
> **0.6.4(当前)** —— 本版三项修复:**D2**(待决 ask 提升为会话级)/ **B1/C1**(规则 24 官方 bundle 豁免兑现 + 装配不一致的收敛豁免)/ **A2**(ask 授权登记改复数路径 + `authMatches` 多路径匹配)。**适用环境验证 = DSH `0.1.5-rc.2`**(装配 + 冷加载探针 + `npm test` + `verify` 13 层全绿)。**发布 commit 待三通道完成后回填**(届时 `git checkout <hash>` 可复现 npm `dsh-rule-engine@0.6.4` 与 GitHub Release v0.6.4 同源代码)。
|
|
270
|
+
|
|
271
|
+
- **0.6.3(当前)** 固定于 main Commit `3e87f9d`(`git checkout 3e87f9d` 可复现 npm `dsh-rule-engine@0.6.3` 与 GitHub Release v0.6.3 同源代码——0.6.3 = 分层残留闸 `dualtrack-check`(判据 A:中文≠个人化)+ 词表全量配置化(lexicons / patterns / criticismPersonal / dualtrack 走 `rule-engine.json`)+ 第三批第 1 批文案层 + 三项门禁修复(`--init` 覆盖保护 / loader-smoke 中文夹具 / 中文目录顿号兼容)。
|
|
272
|
+
- **0.6.2** 固定于 main Commit `92da194538fce2f56fa7c6712c70711865772686`(`git checkout 92da194538fce2f56fa7c6712c70711865772686` 可复现 npm `dsh-rule-engine@0.6.2` 与 GitHub Release v0.6.2 同源代码——0.6.2 = 0.1.2-rc.1 适配(Remote 新合同 `Remote(undefined, …)` / peer 锚扩 `>=0.1.2-rc.0`)+ 白名单 3 轮扩充 + Do- 结构词回归修复 + E7 豁免预插机制上线)。
|
|
273
|
+
- **0.6.1** 固定于 main Commit `051e2da`(`git checkout 051e2da` 可复现 npm `dsh-rule-engine@0.6.1` 与 GitHub Release v0.6.1 同源代码——0.6.1 = 豁免预插(release-plugin bump 后自动追加 pnpm minimumReleaseAgeExclude,⑬ 绝对口径防发布后红灯窗口——踩坑 18 镜像)+ 豁免判定单源化(scripts/lib/pnpm-exempt.mjs 与 verify-all ⑬ 共享,4 单测锁定)+ viewFails 发布语境 STRICT 计 ❌ + ⑬ 块头注释绝对口径(E1/E2/E3 收尾批)。
|
|
121
274
|
- **0.6.0** 固定于 main Commit `be5b8c93`(可复现 `dsh-rule-engine@0.6.0` 与 Release v0.6.0——0.6.0 = 本机集成层(localIntegrations 四键)+ 本机痕迹消号 + li-skipped/entry-script-missing 启动审计 + 发布门禁 B1/B2(readme-version-check / local-residue-scan,挂 verify-all/release-plugin/check:meta)+ check-tool-coverage 素材 fail-closed;词表文件 `scripts/local-residue-markers.txt` 为本机门禁工具,不入库、不进发布物(见 .gitignore / package.json files 排除)。固定源之后的提交仅限 README 指针文本)。
|
|
122
275
|
|
|
123
276
|
## 任务契约与反过度工程(可选)
|
|
@@ -248,10 +401,22 @@ dsh plugin --profile web add dsh-rule-engine
|
|
|
248
401
|
- dsh-visualize(Nagi-ovo)
|
|
249
402
|
- dsh-rules-manager(jilian-dsh)
|
|
250
403
|
- dsh-vision-router、dsh-example-injector 等未列出的作者
|
|
404
|
+
- **设计思想与机制来源(本项目直接内化/借鉴)**:
|
|
405
|
+
- stop-that-shit(lennney):任务边界、反过度工程、预算与四类越界——任务契约模块的设计源头。
|
|
406
|
+
- dsh-agi-harness(yjh051108):闸=最小决策单元、Wilson 下界、拒因即指路、自由面/盲区显式声明、任务签名+质量趋势(quality-ledger)、变异测试+棘轮。
|
|
407
|
+
- Claude Code 权限范式(Anthropic 官方文档):默认询问、deny 永远赢、通配符规则只加不放。
|
|
408
|
+
- mattpocock/skills「writing-for-agents」:写给 agent 的文档方法论。
|
|
409
|
+
- dsh-zvec-grep(sugarforever):后台任务型插件写法。
|
|
410
|
+
- **升级与审查工具链**:
|
|
411
|
+
- oh-my-dsh / dsh-plugin-upgrade-skill(社区):0.1.2 升级卡库与对策集。
|
|
412
|
+
- build-dsh-plugin(AI-Scarlett):插件完备性审计。
|
|
413
|
+
- cordis-plugin-thinking-loop-guard(argszero):纯思考空转的源码级判读。
|
|
251
414
|
- **学习参考的社区文档/库作者**:
|
|
252
415
|
- dsh-handbook(Electricitysheep)
|
|
253
416
|
- SandBase deepseek-harness-handbook(sandbaseai)
|
|
417
|
+
- awesome-dsh-plugin / Oh! dsh(生态目录)
|
|
254
418
|
- 以及 DSH 官方文档镜像与源码维护者
|
|
419
|
+
- **贡献建议与实证的个人**:TheBuilderJR、Reximmortal1021、AI-Scarlett、ckcfcc、goatliamia 等。
|
|
255
420
|
|
|
256
421
|
## 免责声明
|
|
257
422
|
|
|
@@ -266,6 +431,12 @@ bash scripts/build.sh
|
|
|
266
431
|
|
|
267
432
|
交付前体检(0.5.7 起):`node scripts/verify-all.mjs`(七层:语法/单元/组合冒烟/工具箱覆盖/守卫链覆盖/关联一致性/真实判例)与 `node scripts/health-audit.mjs`(找茬)——详见「质量与验证」。
|
|
268
433
|
|
|
434
|
+
**分层残留闸(dualtrack)**:`node scripts/dualtrack-check.mjs` 扫描 `lib/` 是否混入发布者私有内容(本机标识 / 个人规则描述),棘轮式**只许降不许升**;已挂 `npm run check:meta` 与 `verify-all.mjs` 第 ⑩′ 层自动调用。基线 `scripts/dualtrack-baseline.json` **随 git 仓库提供、不进 npm 包**:
|
|
435
|
+
|
|
436
|
+
- **从 git clone 的贡献者无需任何操作**(基线已在仓库里)——**勿在有基线时跑 `--init`**,它会把当前计数覆盖为基线、棘轮当场失效;
|
|
437
|
+
- **仅当基线缺失时**(如从 npm 包解压后跑门禁、或基线被删)首次运行 `node scripts/dualtrack-check.mjs --init` 生成;
|
|
438
|
+
- 词表(哪些字符串算「本机残留」)来自 `rule-engine.json` 的 `dualtrack.markers` → `DUALTRACK_MARKERS` 环境变量 → 包内示例;三者皆空时 **REFUSED**(fail-closed,不会「以为配好了其实没配」)。
|
|
439
|
+
|
|
269
440
|
发布:`node scripts/release-plugin.mjs <插件名> <版本号>`(一键 npm + git + GitHub Release;发布脚本随插件仓库管理——`scripts/release-plugin.mjs`;改发布脚本后须 `--dry-run` + 代码审查,注意 dry-run 不覆盖 git 段)。
|
|
270
441
|
|
|
271
442
|
## License
|
package/lib/core/audit.js
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
// B2(2026-09-03):判定来源可溯源字段约定(判例回灌前置)——
|
|
6
6
|
// verdictSource: "guard"(工具守卫 intercept,机器判定)| "lexicon"(词表直判/降级)
|
|
7
7
|
// | "judge"(LLM 裁决分流)| "llm-intent"(意图 LLM 兜底);缺失 = 历史记录(无此字段)。
|
|
8
|
-
import { appendFileSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { appendFileSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
9
10
|
import { randomUUID } from "node:crypto";
|
|
10
11
|
import { auditFilePath } from "./paths.js";
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
// 审计归档(2026-09-08 第二批 §1.1-②):上限 512KB→2MB;超限时被裁头部先写入
|
|
14
|
+
// <dshHome>/logs/rule-engine-archive/log-YYYYMM.jsonl(按月文件)再保留尾部——历史可回溯。
|
|
15
|
+
const LOG_MAX_BYTES = 2 * 1024 * 1024;
|
|
13
16
|
const LOG_KEEP_LINES = 400;
|
|
14
17
|
const APPEND_TRIM_INTERVAL = 32;
|
|
15
18
|
|
|
@@ -29,13 +32,27 @@ export function audit(entry) {
|
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
/** 被裁头部写入月归档(失败静默——审计裁剪本身容错) */
|
|
36
|
+
function archiveTrimmed(headLines) {
|
|
37
|
+
try {
|
|
38
|
+
const dir = join(dirname(auditFilePath()), "logs", "rule-engine-archive");
|
|
39
|
+
mkdirSync(dir, { recursive: true });
|
|
40
|
+
const ym = new Date().toISOString().slice(0, 7).replace("-", "");
|
|
41
|
+
appendFileSync(join(dir, `log-${ym}.jsonl`), headLines.join("\n") + "\n", "utf8");
|
|
42
|
+
} catch {
|
|
43
|
+
// 归档失败忽略
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
function trimLog() {
|
|
33
48
|
try {
|
|
34
49
|
const p = auditFilePath();
|
|
35
50
|
if (statSync(p).size <= LOG_MAX_BYTES) return;
|
|
36
51
|
const lines = readFileSync(p, "utf8").split("\n");
|
|
37
|
-
const
|
|
38
|
-
|
|
52
|
+
const keptLines = lines.slice(-LOG_KEEP_LINES);
|
|
53
|
+
const head = lines.slice(0, Math.max(0, lines.length - LOG_KEEP_LINES)).filter(Boolean);
|
|
54
|
+
if (head.length) archiveTrimmed(head);
|
|
55
|
+
writeFileSync(p, keptLines.join("\n"), "utf8");
|
|
39
56
|
} catch {
|
|
40
57
|
// 裁剪失败忽略
|
|
41
58
|
}
|
|
@@ -1,36 +1,25 @@
|
|
|
1
1
|
// authorization.js - 授权证据结构化匹配(P0:授权关联具体操作,区分询问与授权)
|
|
2
2
|
// 纯函数,可独立测试。
|
|
3
3
|
import { GENERIC_EXEC_TOOLS, pathTarget, commandText, writeTargetPathsFromCommand, extractCopyPaths } from "./patterns.js";
|
|
4
|
-
import {
|
|
4
|
+
import { questionWordsRe } from "./lexicon.js";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
ACTION_WORDS_RE
|
|
6
|
+
approvalExecRe,
|
|
7
|
+
directiveWordsRe,
|
|
8
|
+
rejectionWordsRe,
|
|
9
|
+
planOnlyRe,
|
|
10
|
+
actionWordsRe
|
|
12
11
|
} from "./lexicon.js";
|
|
13
12
|
|
|
14
13
|
export const AUTH_TTL_MS = 10 * 60 * 1000;
|
|
15
14
|
|
|
16
15
|
// 0.5.11(用户拍板):词表唯一源 = lexicon.js——授权判定所用的许可词/指令词/拒绝词/动作词
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
const DIRECTIVE_RE = DIRECTIVE_WORDS_RE;
|
|
24
|
-
// 纯批准词(选项/自由文本判定用)
|
|
16
|
+
// 一律从那里取;本文件不再自持定义。
|
|
17
|
+
// P8 小批 A(2026-09-08):词表改经函数取(approvalExecRe() 等)——配置层注入后立即生效;
|
|
18
|
+
// 顶层 `const X = SOME_RE` 缓存会锁死内置值,故本文件不再保留任何正则别名。
|
|
19
|
+
// 遗留(本批范围外,如实记账):下面这份 APPROVAL_RE 是本文件自持的第二份许可词定义,
|
|
20
|
+
// 与 lexicon.approval 存在漂移(多"是/执行/去吧/开始"、少"行")——本批保持行为等价不动,
|
|
21
|
+
// 是否合并/迁入配置待第三方裁定(迁入会改变判定结果,不属"等价迁移")。
|
|
25
22
|
const APPROVAL_RE = /(?:允许|同意|可以|是|好|确认|授权|执行|批准|去吧|开始|ok|yes)/i;
|
|
26
|
-
const REJECTION_RE = REJECTION_WORDS_RE;
|
|
27
|
-
|
|
28
|
-
// 0.5.11:执行许可确认词唯一源(lexicon.js APPROVAL_EXEC_RE,组合=许可词[0,16]执行语);
|
|
29
|
-
// 纯"确认方案/理解/方向"不构成(PLAN_ONLY_RE 亦唯一源)。
|
|
30
|
-
|
|
31
|
-
// 选项/自由文本里的明确动作词(用于 ask 结果判定:选择"执行/调整/修复…"即视为批准)
|
|
32
|
-
// 0.5.11:唯一源 = lexicon.js ACTION_WORDS_RE(与 intent 意图判定同表,不再二份)
|
|
33
|
-
const EXEC_ACTION_RE = ACTION_WORDS_RE;
|
|
34
23
|
|
|
35
24
|
const TYPE_HINTS = [
|
|
36
25
|
{ re: /删除|移除|remove|delete/i, type: "delete" },
|
|
@@ -385,13 +374,17 @@ function pathUnderAuth(p, authPath) {
|
|
|
385
374
|
export function authMatches(auth, op) {
|
|
386
375
|
if (!auth || !op) return false;
|
|
387
376
|
if (auth.type !== "any" && op.type !== "any" && auth.type !== op.type) return false;
|
|
388
|
-
|
|
377
|
+
// A2(2026-09-10):授权侧支持多路径——ask 多路径问题文本经 index.js L1029 记入 pathPrefixes。
|
|
378
|
+
// 语义:任一授权路径覆盖任一操作候选路径即匹配(此前只认单一 auth.pathPrefix,ask 侧只记一条 → 其余路径的授权形同虚设)。
|
|
379
|
+
const authPaths = Array.isArray(auth.pathPrefixes) && auth.pathPrefixes.length > 0
|
|
380
|
+
? auth.pathPrefixes.map(normalizePath).filter(Boolean)
|
|
381
|
+
: (auth.pathPrefix ? [normalizePath(auth.pathPrefix)] : []);
|
|
382
|
+
if (authPaths.length > 0) {
|
|
389
383
|
const candidates = Array.isArray(op.pathPrefixes) && op.pathPrefixes.length > 0
|
|
390
384
|
? op.pathPrefixes
|
|
391
385
|
: (op.pathPrefix ? [op.pathPrefix] : []);
|
|
392
386
|
if (candidates.length === 0) return false;
|
|
393
|
-
|
|
394
|
-
if (!candidates.some((p) => pathUnderAuth(normalizePath(p), authPath))) return false;
|
|
387
|
+
if (!candidates.some((p) => authPaths.some((a) => pathUnderAuth(normalizePath(p), a)))) return false;
|
|
395
388
|
}
|
|
396
389
|
// 柱子 B(2026-08-31):对象锚定——授权声明了显式命名对象 → 操作命令文本必须命中该对象
|
|
397
390
|
//("推送 X 仓库"不覆盖"推送 Y 仓库";未锚定对象=不收紧)
|
|
@@ -413,7 +406,11 @@ export function findMatchingAuth(auths, op, now = Date.now()) {
|
|
|
413
406
|
export function describeAuth(auth) {
|
|
414
407
|
if (!auth) return "无";
|
|
415
408
|
const type = auth.type || "any";
|
|
416
|
-
|
|
409
|
+
// A2(2026-09-10):多路径授权逐条显示(ask 多路径问题文本);单路径/全局维持原措辞。
|
|
410
|
+
const paths = Array.isArray(auth.pathPrefixes) && auth.pathPrefixes.length > 0
|
|
411
|
+
? auth.pathPrefixes
|
|
412
|
+
: (auth.pathPrefix ? [auth.pathPrefix] : []);
|
|
413
|
+
const path = paths.length === 0 ? "全局" : paths.length === 1 ? `路径 ${paths[0]}` : `路径 ${paths.join(" / ")}`;
|
|
417
414
|
const expires = auth.expiresAt ? `|到期 ${new Date(auth.expiresAt).toISOString()}` : "";
|
|
418
415
|
return `${type}|${path}|${new Date(auth.at || 0).toISOString()}${expires}|仅本次运行有效`;
|
|
419
416
|
}
|
|
@@ -425,31 +422,31 @@ export function describeOp(op) {
|
|
|
425
422
|
|
|
426
423
|
/** 用户消息是否为“询问影响”而非授权(词表与 intent.js 统一,2026-08-24) */
|
|
427
424
|
export function isQuestionMessage(text) {
|
|
428
|
-
return
|
|
425
|
+
return questionWordsRe().test(String(text || ""));
|
|
429
426
|
}
|
|
430
427
|
|
|
431
428
|
/** 用户消息是否包含明确执行许可(确认/同意/授权 + 执行语;且不是询问句) */
|
|
432
429
|
export function isAuthMessage(text) {
|
|
433
430
|
const s = String(text || "");
|
|
434
|
-
return
|
|
431
|
+
return approvalExecRe().test(s) && !isQuestionMessage(s);
|
|
435
432
|
}
|
|
436
433
|
|
|
437
434
|
/** 用户消息是否为直接命令式指令(非询问,视为授权执行) */
|
|
438
435
|
export function isDirectiveMessage(text) {
|
|
439
436
|
const s = String(text || "");
|
|
440
|
-
return
|
|
437
|
+
return directiveWordsRe().test(s) && !isQuestionMessage(s);
|
|
441
438
|
}
|
|
442
439
|
|
|
443
440
|
/** 选项/自由文本是否表示批准(纯"确认方案/理解/方向"不算批准) */
|
|
444
441
|
export function isApprovalText(text) {
|
|
445
442
|
const s = String(text || "");
|
|
446
|
-
if (
|
|
447
|
-
return APPROVAL_RE.test(s) ||
|
|
443
|
+
if (planOnlyRe().test(s) && !actionWordsRe().test(s) && !approvalExecRe().test(s)) return false;
|
|
444
|
+
return APPROVAL_RE.test(s) || actionWordsRe().test(s);
|
|
448
445
|
}
|
|
449
446
|
|
|
450
447
|
/** 选项/自由文本是否表示拒绝 */
|
|
451
448
|
export function isRejectionText(text) {
|
|
452
|
-
return
|
|
449
|
+
return rejectionWordsRe().test(String(text || ""));
|
|
453
450
|
}
|
|
454
451
|
|
|
455
452
|
/** 从 ask_user_question 的 arguments 中提取问题文本(用于推断授权范围) */
|
package/lib/core/config.js
CHANGED
|
@@ -32,7 +32,10 @@ export const DEFAULT_CONFIG = {
|
|
|
32
32
|
handlerOverrides: {},
|
|
33
33
|
// 残余1 剥离(2026-08-31):本机默认偏好表下沉配置——{ ruleId: handlerName }
|
|
34
34
|
// 通用发布=空(代码零本机编号);本机在此写入 17 条偏好,引擎升级不丢。
|
|
35
|
-
handlerDefaultMap: {}
|
|
35
|
+
handlerDefaultMap: {},
|
|
36
|
+
// §1.3(2026-09-08 第二批):本机辱骂词表(个人话术,发布面不含)——空=不启用;
|
|
37
|
+
// 通用层只保留语言无关形态检测(连续问叹号/全大写比率),语义判定归 LLM。
|
|
38
|
+
criticismPersonal: []
|
|
36
39
|
};
|
|
37
40
|
|
|
38
41
|
export function loadPluginConfig() {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// dualtrack-markers.js - 本机标识词表的**唯一加载器**(B2 扫描器与 dualtrack-check 共用)
|
|
2
|
+
//
|
|
3
|
+
// 设计(第三批第 1 批 · 词表配置化,用户拍板 2026-09-09):
|
|
4
|
+
// 词表是**发布者私有数据**——「我这台机器长什么样」对别人毫无意义,因此:
|
|
5
|
+
// ① 优先读本机 rule-engine.json 的 `dualtrack.markers`(字符串数组);
|
|
6
|
+
// ② 其次读环境变量 DUALTRACK_MARKERS 指向的文件(每行一个,`#` 注释);
|
|
7
|
+
// ③ 最后回退包内 scripts/local-residue-markers.txt(**示例**,发布者应换成本机配置)。
|
|
8
|
+
// 包内那份在公开仓库里只保留示例行,避免把发布者的用户名/路径公开出去。
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
|
|
13
|
+
/** 解析 DSH_HOME(与 dsh-home-paths 口径一致) */
|
|
14
|
+
export function resolveDshHome() {
|
|
15
|
+
return process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readMarkerFile(file) {
|
|
19
|
+
if (!file || !fs.existsSync(file)) return null;
|
|
20
|
+
const lines = fs.readFileSync(file, "utf8").split(/\r?\n/)
|
|
21
|
+
.map((s) => s.trim())
|
|
22
|
+
.filter((s) => s && !s.startsWith("#"));
|
|
23
|
+
return lines.length > 0 ? lines : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 加载本机标识词表。
|
|
28
|
+
* @param {object} opts
|
|
29
|
+
* @param {string} opts.root 仓库根(回退示例文件所在)
|
|
30
|
+
* @param {object} [opts.config] 已加载的 rule-engine.json 内容(可选;缺省自行读取)
|
|
31
|
+
* @returns {{markers: string[], source: string}}
|
|
32
|
+
*/
|
|
33
|
+
export function loadMarkers(opts = {}) {
|
|
34
|
+
const root = opts.root || process.cwd();
|
|
35
|
+
|
|
36
|
+
// ① 本机配置:rule-engine.json 的 dualtrack.markers
|
|
37
|
+
let cfg = opts.config;
|
|
38
|
+
if (cfg === undefined) {
|
|
39
|
+
try {
|
|
40
|
+
const p = path.join(resolveDshHome(), "rule-engine.json");
|
|
41
|
+
cfg = fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, "utf8")) : null;
|
|
42
|
+
} catch {
|
|
43
|
+
cfg = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const fromCfg = cfg?.dualtrack?.markers;
|
|
47
|
+
if (Array.isArray(fromCfg)) {
|
|
48
|
+
const markers = fromCfg.filter((x) => typeof x === "string" && x.trim().length > 0).map((x) => x.trim());
|
|
49
|
+
if (markers.length > 0) return { markers, source: "rule-engine.json:dualtrack.markers" };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ② 环境变量指向的本机文件
|
|
53
|
+
const fromEnv = readMarkerFile(process.env.DUALTRACK_MARKERS);
|
|
54
|
+
if (fromEnv) return { markers: fromEnv, source: `env DUALTRACK_MARKERS=${process.env.DUALTRACK_MARKERS}` };
|
|
55
|
+
|
|
56
|
+
// ③ 包内示例文件(发布者应换成本机配置;此处可能只有注释示例)
|
|
57
|
+
const fallback = readMarkerFile(path.join(root, "scripts", "local-residue-markers.txt"));
|
|
58
|
+
if (fallback) return { markers: fallback, source: "scripts/local-residue-markers.txt (in-package example)" };
|
|
59
|
+
|
|
60
|
+
return { markers: [], source: "empty" };
|
|
61
|
+
}
|