dsh-rule-engine 0.5.11 → 0.5.13
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 +30 -99
- package/lib/core/authorization.js +517 -513
- package/lib/core/config.js +73 -66
- package/lib/core/contract.js +41 -0
- package/lib/core/guard-core.js +754 -724
- package/lib/core/intent.js +11 -3
- package/lib/core/label-fingerprint.js +145 -0
- package/lib/core/lexicon.js +72 -72
- package/lib/core/parser.js +111 -103
- package/lib/core/patterns.js +42 -3
- package/lib/core/state.js +324 -301
- package/lib/core/understander.js +221 -150
- package/lib/index.js +1788 -1663
- package/lib/service.js +17 -2
- package/package.json +1 -1
- package/scripts/publish-aptitude-check.mjs +144 -0
- package/scripts/verify-all.mjs +4 -0
package/lib/core/understander.js
CHANGED
|
@@ -1,150 +1,221 @@
|
|
|
1
|
-
// understander.js - 规则理解器(模式库兜底版)。
|
|
2
|
-
// 输入 parser 解析出的规则,输出结构化执行配置。
|
|
3
|
-
// LLM 理解器可在后续版本接入 ctx.llm,当前先保证确定性与可测试性。
|
|
4
|
-
import { extractElements } from "./parser.js";
|
|
5
|
-
import {
|
|
6
|
-
BOM_WRITE,
|
|
7
|
-
DESTRUCTIVE_CMD,
|
|
8
|
-
DSH_KEYWORDS_RE,
|
|
9
|
-
INLINE_CMD,
|
|
10
|
-
SENSITIVE_CMD,
|
|
11
|
-
TIME_WORDS,
|
|
12
|
-
PROMISE_WORDS,
|
|
13
|
-
URL_RE,
|
|
14
|
-
SOURCE_MARK
|
|
15
|
-
} from "./patterns.js";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
1
|
+
// understander.js - 规则理解器(模式库兜底版)。
|
|
2
|
+
// 输入 parser 解析出的规则,输出结构化执行配置。
|
|
3
|
+
// LLM 理解器可在后续版本接入 ctx.llm,当前先保证确定性与可测试性。
|
|
4
|
+
import { extractElements, levelFromTitle } from "./parser.js";
|
|
5
|
+
import {
|
|
6
|
+
BOM_WRITE,
|
|
7
|
+
DESTRUCTIVE_CMD,
|
|
8
|
+
DSH_KEYWORDS_RE,
|
|
9
|
+
INLINE_CMD,
|
|
10
|
+
SENSITIVE_CMD,
|
|
11
|
+
TIME_WORDS,
|
|
12
|
+
PROMISE_WORDS,
|
|
13
|
+
URL_RE,
|
|
14
|
+
SOURCE_MARK
|
|
15
|
+
} from "./patterns.js";
|
|
16
|
+
|
|
17
|
+
// 2026-08-31(残余1 剥离,用户定稿):本机默认偏好表从代码下沉到本机配置——
|
|
18
|
+
// rule-engine.json 的 handlerDefaultMap(可选键,可整体替换/清空)。代码层默认表为空
|
|
19
|
+
// (通用版=零本机编号);本机偏好经 state 注入(understandRule/understandAll 的 opts.defaultMap)。
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 覆盖自省(一致性预防机制):
|
|
23
|
+
* - dead:defaultMap 映射了但 AGENTS.md 无此规则 ID(死映射,应清理)
|
|
24
|
+
* - uncovered:A/C/M(deny/ask/meta)级但无 handler 的规则(规则声称会被机器执行,实际不会)
|
|
25
|
+
* 在 /guard status 与一致性测试/脚本中使用;规则 21④/19⑦ 的体检触发条件之一。
|
|
26
|
+
* 剥离后 defaultMap 由调用方传入(本机=配置表;通用=空表);缺省空表 → dead 恒 0。
|
|
27
|
+
* @param {object} configs 理解产物
|
|
28
|
+
* @param {object} [defaultMap] 生效的默认偏好表({ruleId: handlerName})
|
|
29
|
+
*/
|
|
30
|
+
export function analyzeCoverage(configs, defaultMap = {}) {
|
|
31
|
+
const dead = new Map(); // ruleId -> handler(映射存在但规则不存在)
|
|
32
|
+
const uncovered = []; // A/C/M 级规则但引擎无执行 handler
|
|
33
|
+
const present = new Set();
|
|
34
|
+
for (const cfg of configs || []) {
|
|
35
|
+
if (cfg && cfg.ruleId !== undefined && cfg.ruleId !== null) present.add(String(cfg.ruleId));
|
|
36
|
+
}
|
|
37
|
+
for (const [id, handler] of Object.entries(defaultMap || {})) {
|
|
38
|
+
if (!present.has(String(id))) dead.set(String(id), handler);
|
|
39
|
+
}
|
|
40
|
+
for (const cfg of configs || []) {
|
|
41
|
+
if (cfg.disabled) continue;
|
|
42
|
+
const acts = cfg.actions || [];
|
|
43
|
+
const hard = acts.some((a) => a === "deny" || a === "ask" || a === "meta");
|
|
44
|
+
if (hard && !cfg.handler) {
|
|
45
|
+
uncovered.push({ ruleId: String(cfg.ruleId), title: cfg.title, actions: acts });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { dead, uncovered };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 根据执行等级推导动作(容忍空白/强弱/连接符:如 "B + D"、"A 弱 + D"、"D 强") */
|
|
52
|
+
export function actionsForLevel(level) {
|
|
53
|
+
const s = String(level || "").toUpperCase().replace(/\s+/g, "");
|
|
54
|
+
const actions = [];
|
|
55
|
+
if (s.includes("A")) actions.push("deny");
|
|
56
|
+
if (s.includes("B")) actions.push("correct");
|
|
57
|
+
if (s.includes("C")) actions.push("ask");
|
|
58
|
+
if (s.includes("D")) actions.push("self-certify");
|
|
59
|
+
if (s.includes("M")) actions.push("meta");
|
|
60
|
+
if (actions.length === 0) actions.push("self-certify");
|
|
61
|
+
return [...new Set(actions)];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function splitKeywords(text) {
|
|
65
|
+
if (!text) return [];
|
|
66
|
+
return text
|
|
67
|
+
.split(/[,。;、,\n;::/\\()()]+/)
|
|
68
|
+
.map((s) => s.trim())
|
|
69
|
+
.filter((s) => s.length >= 2 && s.length <= 20);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 从检查文本提取机器可用的提示词 */
|
|
73
|
+
function hintPatterns(checkText) {
|
|
74
|
+
const hints = [];
|
|
75
|
+
if (/node\s+-e|node\s+-p|pwsh\s+-c|--eval|--print|-Command\b/i.test(checkText)) hints.push("inline-command");
|
|
76
|
+
if (/set-content|out-file|add-content|writealltext|utf8bom/i.test(checkText)) hints.push("bom-write");
|
|
77
|
+
if (/ask_user_question|授权|弹框/i.test(checkText)) hints.push("ask");
|
|
78
|
+
if (/get-date|时间词|昨天|今天/i.test(checkText)) hints.push("time");
|
|
79
|
+
if (/skill|技能/i.test(checkText)) hints.push("skill");
|
|
80
|
+
if (/git\s+(push|commit)|敏感操作|授权/i.test(checkText)) hints.push("sensitive");
|
|
81
|
+
if (/删除|覆盖|备份|验证/i.test(checkText)) hints.push("backup");
|
|
82
|
+
if (/手册/i.test(checkText)) hints.push("manual");
|
|
83
|
+
if (/重试|连续失败|第\s*3\s*次/i.test(checkText)) hints.push("retry");
|
|
84
|
+
if (/运行时验证|mock|启动|实测/i.test(checkText)) hints.push("runtime-verify");
|
|
85
|
+
if (/URL|来源|出处|引用/i.test(checkText)) hints.push("source");
|
|
86
|
+
if (/中文|英文|语言/i.test(checkText)) hints.push("language");
|
|
87
|
+
return [...new Set(hints)];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 规则正文内联 handler 声明(T3):`<!-- handler: rule22-7-direct -->` */
|
|
91
|
+
const HANDLER_DECL_RE = /<!--\s*handler\s*:\s*([a-z0-9][a-z0-9-]*)\s*-->/i;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 执行器语义别名(残余3 通用化,2026-08-31):
|
|
95
|
+
* 内部执行器名(rule12a-approval 等)带规则编号影子——陌生用户声明时不应见到。
|
|
96
|
+
* 语义名 → 内部名映射:声明/配置写语义名即归一;直接写内部名不受影响(别名表无该键)。
|
|
97
|
+
* 归一发生在 resolveHandler 末端;guard-core 分支(按内部名)零改动。
|
|
98
|
+
*/
|
|
99
|
+
const HANDLER_ALIASES = {
|
|
100
|
+
retry: "rule1-retry",
|
|
101
|
+
time: "rule2-time",
|
|
102
|
+
source: "rule5-source",
|
|
103
|
+
promise: "rule7-promise",
|
|
104
|
+
"inline-command": "rule9-inline-bom",
|
|
105
|
+
language: "rule11-language",
|
|
106
|
+
approval: "rule12a-approval",
|
|
107
|
+
"skill-auth": "rule12b-skill",
|
|
108
|
+
network: "rule12c-network",
|
|
109
|
+
backup: "rule13a-backup",
|
|
110
|
+
"manual-first": "rule18-manual-first",
|
|
111
|
+
meta: "rule21-meta",
|
|
112
|
+
"intent-direct": "rule22-7-direct",
|
|
113
|
+
"runtime-verify": "rule23-runtime-verify",
|
|
114
|
+
assembly: "rule24-assembly-type",
|
|
115
|
+
"release-asset": "rule26-release-asset",
|
|
116
|
+
"mount-audit": "rule27-mount-audit"
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/** 语义名归一(内部名原样返回;未知名保持原样——由覆盖自省提示未覆盖) */
|
|
120
|
+
export function normalizeHandlerName(name) {
|
|
121
|
+
const s = String(name || "");
|
|
122
|
+
return HANDLER_ALIASES[s] || s;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* handler 绑定解析(T3 通用化,2026-08-31)——优先级:
|
|
127
|
+
* ① opts.handlerOverrides[ruleId] (rule-engine.json 配置覆盖,集中管理)
|
|
128
|
+
* ② 规则正文内联声明 <!-- handler: xxx --> (随规则走,可声明任意已实现执行器)
|
|
129
|
+
* ③ opts.defaultMap || HANDLER_BY_RULE (本机默认偏好表;defaultMap 可整体替换)
|
|
130
|
+
* ④ "" (未绑定=纯自证规则,README「局限 4」分流——规则仍参与匹配/自证,不参与硬拦)
|
|
131
|
+
* 所有来源经 normalizeHandlerName 归一(语义名/内部名均可;本机零行为变化)。
|
|
132
|
+
*/
|
|
133
|
+
export function resolveHandler(rule, opts = {}) {
|
|
134
|
+
const id = String(rule?.index ?? "");
|
|
135
|
+
const overrides = opts?.handlerOverrides;
|
|
136
|
+
if (overrides && typeof overrides === "object" && overrides[id] !== undefined) return normalizeHandlerName(overrides[id]);
|
|
137
|
+
const body = String(rule?.body || "");
|
|
138
|
+
const decl = body.match(HANDLER_DECL_RE);
|
|
139
|
+
if (decl) return normalizeHandlerName(decl[1]);
|
|
140
|
+
// 剥离后(2026-08-31):代码层无默认表;defaultMap 由调用方注入(本机=配置 handlerDefaultMap)
|
|
141
|
+
const map = opts?.defaultMap || {};
|
|
142
|
+
return normalizeHandlerName(map[rule?.index] || "");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 理解一条规则。
|
|
147
|
+
* @param {object} rule parser 输出
|
|
148
|
+
* @param {object} [opts] 绑定选项(handlerOverrides/defaultMap,见 resolveHandler)
|
|
149
|
+
* @returns {object} 执行配置
|
|
150
|
+
*/
|
|
151
|
+
export function understandRule(rule, opts = {}) {
|
|
152
|
+
const elems = extractElements(rule.body || "");
|
|
153
|
+
const level = rule.level || "";
|
|
154
|
+
const actions = actionsForLevel(level);
|
|
155
|
+
const hints = hintPatterns(elems.check);
|
|
156
|
+
const triggerKeywords = splitKeywords(elems.trigger);
|
|
157
|
+
const confidence = level && elems.trigger && elems.check && (elems.action || level.toUpperCase().includes("D")) ? "high" : level ? "medium" : "low";
|
|
158
|
+
const handler = resolveHandler(rule, opts);
|
|
159
|
+
const config = {
|
|
160
|
+
ruleId: rule.index,
|
|
161
|
+
title: rule.title,
|
|
162
|
+
section: rule.section,
|
|
163
|
+
level,
|
|
164
|
+
actions,
|
|
165
|
+
confidence,
|
|
166
|
+
handler,
|
|
167
|
+
triggerKeywords,
|
|
168
|
+
hints,
|
|
169
|
+
elements: elems,
|
|
170
|
+
disabled: false
|
|
171
|
+
};
|
|
172
|
+
return config;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** 批量理解。opts:
|
|
176
|
+
* - disabledEntries: dsh-rules-manager 禁用存档条目(数组)。正文已移走、不在 rules 中的
|
|
177
|
+
* 禁用规则用存档正文重建占位 cfg(disabled=true)——禁用=存在但休眠,恢复自动生效。
|
|
178
|
+
* 在理解层重建保证所有调用方(state/consistency-live/运行时)口径一致。
|
|
179
|
+
* - handlerOverrides: { ruleId: handlerName } 声明式绑定覆盖(T3,通用化通道)。
|
|
180
|
+
* - defaultMap: 覆盖 HANDLER_BY_RULE 默认偏好表(T3;缺省=本机默认表)。
|
|
181
|
+
*/
|
|
182
|
+
export function understandAll(rules, opts = {}) {
|
|
183
|
+
const configs = (rules || []).map((r) => understandRule(r, opts));
|
|
184
|
+
const entries = opts?.disabledEntries;
|
|
185
|
+
if (Array.isArray(entries) && entries.length) {
|
|
186
|
+
const disabledIds = new Set(entries.map((d) => String(d.index)).filter(Boolean));
|
|
187
|
+
// 在场禁用规则:只标 disabled(保留 AGENTS.md 正文,不被存档覆盖)
|
|
188
|
+
for (const cfg of configs) {
|
|
189
|
+
if (disabledIds.has(String(cfg.ruleId))) cfg.disabled = true;
|
|
190
|
+
}
|
|
191
|
+
// 缺场禁用规则:用存档正文重建占位(正文已移走的规则不再"消失")
|
|
192
|
+
const present = new Set(configs.map((c) => String(c.ruleId)));
|
|
193
|
+
for (const entry of entries) {
|
|
194
|
+
const key = String(entry.index);
|
|
195
|
+
if (present.has(key)) continue;
|
|
196
|
+
const cfg = understandRule({
|
|
197
|
+
index: entry.index,
|
|
198
|
+
title: entry.title || "",
|
|
199
|
+
section: entry.section || "",
|
|
200
|
+
level: levelFromTitle(entry.header || entry.title || ""),
|
|
201
|
+
body: entry.body || ""
|
|
202
|
+
}, opts);
|
|
203
|
+
cfg.disabled = true;
|
|
204
|
+
configs.push(cfg);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return configs;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** 导出常用正则供测试/调试 */
|
|
211
|
+
export const REGEX = {
|
|
212
|
+
INLINE_CMD,
|
|
213
|
+
BOM_WRITE,
|
|
214
|
+
DESTRUCTIVE_CMD,
|
|
215
|
+
SENSITIVE_CMD,
|
|
216
|
+
TIME_WORDS,
|
|
217
|
+
PROMISE_WORDS,
|
|
218
|
+
URL_RE,
|
|
219
|
+
SOURCE_MARK,
|
|
220
|
+
DSH_KEYWORDS_RE
|
|
221
|
+
};
|