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.
@@ -1,72 +1,177 @@
1
- // lexicon.js - 规则 22 语义判定的唯一词表源(0.5.11,用户拍板"唯一词表源由引擎维护")。
2
- // 背景(2026-08-28 全量审查):正文曾手抄词表(L144/148),引擎内部 intent.js 与 authorization.js
3
- // 又各写一份(许可词在 authorization 出现 3 次:AUTH_WORDS_RE/APPROVAL_RE/APPROVAL_EXEC_RE)——
4
- // 同语义多处定义 = 漂移根源。本文件 = 五表唯一源:
5
- // 执行许可词表(许可词 + 执行语,两者缺一才不构成许可)
6
- // 执行动作词表(意图/授权判定共用)
7
- // ③ 疑问词表(问询分点判定)
8
- // 状态信号词表("我已重启/好了/done"等,状态信号不触发无执行分点)
9
- // 豁免工具清单(规则 22 ③ "只读/展示/ask/todo_write 等豁免"的明细)
10
- // 调用方(intent.js/authorization.js)一律从这里 import,禁止再定义第二份。
11
- // 设计原则:词表可以在,但只能有一份;语义变更改这里,正文措辞改 AGENTS.md,二者成对。
12
-
13
- // ── ① 执行许可词表 ──
14
- // 许可词:用户确认/同意/批准/授权/可以/允许/好/行/ok/yes + 执行语(即可/现在/马上/…)
15
- // 组合规则:许可词 [0,16 字符内] 执行语 → 构成执行许可;纯"确认方案/理解/方向"不构成。
16
- export const APPROVAL_WORDS_RE = /(?:确认|同意|批准|授权|可以|允许|好|行|ok|yes)/i;
17
- export const EXEC_FOLLOW_RE = /(?:即可|现在|马上|继续|开始|执行|落盘|去做|实施|进行|删除|修改|改|补|修|做|写入|创建|安装|发布|下载|提交|运行)/i;
18
- export const APPROVAL_EXEC_RE = new RegExp(
19
- APPROVAL_WORDS_RE.source.replace(/\\\//g, "/") +
20
- "[^\\n]{0,16}" +
21
- EXEC_FOLLOW_RE.source.replace(/\\\//g, "/"),
22
- "i"
23
- );
24
- // 纯方案/理解/方向确认(不构成执行授权)
25
- export const PLAN_ONLY_RE = /确认方案|确认理解|确认方向|确认无误|理解了|明白方向|了解|没问题|收到/i;
26
-
27
- // ── ② 执行动作词表(intent.ACTION_RE / authorization.EXEC_ACTION_RE 的合并唯一源)──
28
- // 注意:单一表同时服务"意图判定"(这句话是不是执行分点)与"ask 结果批准判定"(选择=批准),
29
- // 语义一致,不另分裂;新增动作词只改这里。
30
- export const ACTION_WORDS_RE =
31
- /执行|跑|落|落盘|认可|开始|写入|写(?:下|好|完|成|作|文件)?|保存|另存为|删除|修改|改(?:为|成|动|一下|进|善)?|补(?:上|齐|全|充|写)?|修(?:改|复|一下)?|做(?:好|完|一下)?|创建|建(?:设|立|好|一下|个|一个)?|安装|发布|下载|提交|推送|push|运行|修复|替换|重建|启动|重启|停止|卸载|开始改|开始写|改进|优化|增强|完善|升级|迁移|整理|调整|实现|实施|添加|增加|补充|重写|改造|调试|排查|处理|解决|继续|推进|更新|部署|核实|梳理|诊断|对齐|跟进|落实|设计并实现|调查|开工|动手|进行|完成|选择|采纳|选定|产出|编写|撰写|生成|审查|阅读|查看|核对|检查|审阅|复核|查阅|验证|对照|调研|研究|转化|转换|读取|读(?:出|一下|一遍|完|出来)?|展示|打开|提取|还原|导入|导出|清理|移除|清空|残留|合入|合并|并入|融合|整合|起草|开展|搭建|重构|兼容|投产|校验|核验|过一遍|跑一遍|复验/i;
32
-
33
- // 强执行语(直接执行指令,不等授权确认——用于意图置信提升)
34
- export const STRONG_EXEC_RE =
35
- /执行吧|开始执行|直接执行|立即执行|现在执行|马上执行|请落盘|开始写|开始改|执行以下|执行第|执行这个|执行它|落盘|开始蒸馏|开始迁移|开始修复|开始改进|开始优化|采纳|选定/i;
36
-
37
- // 指令式动作词(用户命令式指令 = 视为授权执行;与批准词不同源、用途不同)
38
- export const DIRECTIVE_WORDS_RE =
39
- /(?:请\s*)?(?:删除|移除|修改|编辑|替换|写入|写(?:下|好|完|成|作|文件)?|保存|另存为|创建|复制|移动|执行|运行|下载|提交|推送|备份|安装|卸载|清理|启动|停止|重建|修复|改(?:为|成|动|一下)?|补(?:上|齐|全|充|写)?|修(?:改|复|一下)?|做(?:好|完|一下)?|实施|推进|继续|处理|解决)/i;
40
-
41
- // 拒绝词(授权/批准判定用)
42
- export const REJECTION_WORDS_RE = /(?:不允许|拒绝|不要|不同意|取消(?:这次|并停)?|(?:^|[^是])否(?:[^则]|$)|no(?!tes?|thing|pe)\b|\bno\b|No)/i;
43
-
44
- // ── ③ 疑问词表(问询分点判定;authorization.isQuestionMessage 同源)──
45
- export const QUESTION_WORDS_RE =
46
- /[??]|吗|为什么|是否|能不能|行不行|难道|是不是|能否|怎么|为何|是否要|影响吗|可以吗|要不要|需不需要/;
47
-
48
- // ── 状态信号词表("我已重启/已输入/好了/done";触发时不判"无执行分点")──
49
- export const STATUS_SIGNAL_WORDS_RE =
50
- /(?:我已|我已经|我(?:们)?(?:刚)?(?:重启|输入|执行|完成|搞定|做完|弄好|输完|输好))(?:了|完毕|完成|好)?|(?:^|\s)(?:已|已经)?(?:重启|输入|执行|完成|搞定|做完|弄好|输完|输好|unlock)(?:了|完毕|完成|好)?(?:\s|$)|(?:^|\s)已经(?:刚)?(?:重启|输入|执行|完成|搞定|做完|弄好|输完|输好|unlock)[^\s]*(?:\s|$)|(?:^|\s)(?:重启|输入|命令|操作)(?:完成|完毕|好了|已输入)(?:\s|$)|(?:^|\s)好了(?:\s|$)|^(?:done|restarted|finished|entered)$/i;
51
- // 危险动作词:命中则不当作状态信号(防"我已删除了某文件"被当成就绪确认)
52
- export const DANGEROUS_ACTION_WORDS_RE = /删除|覆盖|移除|清空|卸载|remove-item|rm\s+-r|del\s+/i;
53
-
54
- // ── ⑤ 豁免工具清单(规则 22 ③ "只读/展示/ask/todo_write 等豁免"明细)──
55
- // 只读/展示/询问/产物类工具在"无执行分点"回合也可调用(引擎 tool-catalog 的 analysis/artifact 分类
56
- // 为总开关;本清单 = 规则正文"等豁免"所指的可枚举集,与 tool-catalog 一致,此处集中注释成对)。
57
- export const EXEMPT_TOOL_NAMES = new Set([
58
- // 只读/查询
59
- "read", "grep", "glob", "read_image",
60
- // 视觉分析
61
- "vision_describe", "vision_detect", "vision_ground", "vision_colors", "vision_ocr", "vision_bootstrap",
62
- // 状态查询
63
- "job_list", "job_output", "list_agents", "get_goal", "schedule_list",
64
- "engram_recall", "engram_detail", "engram_model",
65
- "esr_status", "esr_ready", "esr_model",
66
- // 网络只读
67
- "web_fetch", "web_search",
68
- // 询问/展示/产物
69
- "ask_user_question", "todo_write", "visualize", "report",
70
- // inspect 只读
71
- "cordis_inspect_list", "cordis_inspect_query", "cordis_inspect_self"
72
- ]);
1
+ // lexicon.js - 规则引擎行为词表的唯一源(0.5.11"唯一词表源";P8 小批 A 起双层化)。
2
+ //
3
+ // 双层模型(P8 小批 A,2026-09-08):
4
+ // 通用层(内置默认 BUILTIN_LEXICONS)——语言无关最小集,随包发布,不含任何中文/本机行为词;
5
+ // 个人层(rule-engine.json lexicons 键)——null/缺省 = 用内置默认;非空 = 按键完全替换。
6
+ // 机制同 setTypeHints(Override 模式):不做合并,配置即真相,删键即回退。
7
+ //
8
+ // 为什么取词必须走函数(lexRe / xxxRe())而不能缓存正则对象:
9
+ // 配置注入发生在模块求值之后(index.js 启动时 setLexicons)。若调用方在模块顶层
10
+ // `const X = SOME_RE` 缓存引用,注入后旧引用永不更新——这正是本批要根除的形态。
11
+ //
12
+ // 词表键(LEXICON_KEYS,11 张)与语义见 README「词表配置」章节。
13
+
14
+ /** 词表键名(顺序即文档顺序;approval_exec 为派生键,可显式覆盖) */
15
+ export const LEXICON_KEYS = [
16
+ "approval", // 执行许可词("确认/同意/可以…")
17
+ "exec_follow", // 执行语(许可词 + 执行语 = 执行许可)
18
+ "approval_exec", // 派生:approval + 0..16 字符 + exec_follow(可显式覆盖)
19
+ "plan_only", // ① 纯方案/理解确认(不构成执行授权)
20
+ "action_words", // ② 执行动作词(意图判定与授权判定共用)
21
+ "strong_exec", // ② 强执行语(直接执行指令,不等授权确认)
22
+ "directive", // ② 指令式动作词(命令式指令 = 视为授权执行)
23
+ "rejection", // ② 拒绝词
24
+ "question", // ③ 疑问词(问询分点判定)
25
+ "status_signal", // 状态信号词(就绪确认,不判"无执行分点")
26
+ "dangerous_action" // ④ 危险动作词(命中则不当作状态信号)
27
+ ];
28
+
29
+ // ── 通用层:内置默认(语言无关最小集)────────────────────────────────────────
30
+ // 约束:本表随 npm 包发布 → 不得含中文或任何本机专属行为词;
31
+ // 中文词表由本机 rule-engine.json.lexicons 注入(缺省时引擎按此最小集工作)。
32
+ const BUILTIN_LEXICONS = {
33
+ approval: "(?:ok|yes|y|approve|approved|agree|agreed|allow|allowed|permission granted)",
34
+ exec_follow: "(?:now|go ahead|proceed|continue|start|execute|run|apply|do it|write|save|delete|modify|create|install|publish|download|commit|push|fix|replace|rebuild|restart|stop|uninstall|update|deploy)",
35
+ plan_only: "confirm (?:the )?plan|understood|got it|acknowledged|noted|received",
36
+ action_words: "(?:execute|run|apply|write|save|delete|modify|edit|create|install|publish|download|commit|push|fix|replace|rebuild|start|restart|stop|uninstall|improve|optimize|enhance|upgrade|migrate|tidy|adjust|implement|add|append|rewrite|refactor|debug|investigate|resolve|continue|advance|update|deploy|verify|comb|diagnose|align|follow up|design and implement|survey|kick off|proceed|complete|choose|adopt|select|produce|author|write up|generate|review|read|inspect|check|audit|recheck|consult|validate|compare|research|study|convert|transform|fetch|read out|display|open|extract|restore|import|export|clean|remove|purge|merge|integrate|draft|carry out|build|make compatible|validate|re-verify)",
37
+ strong_exec: "(?:execute now|start now|do it|go ahead|proceed now|apply now|start writing|start editing|carry it out|adopt|selected)",
38
+ directive: "(?:please\\s+)?(?:delete|remove|modify|edit|replace|write|save|create|copy|move|execute|run|download|commit|push|backup|install|uninstall|clean|start|stop|rebuild|fix|implement|advance|continue|handle|resolve)",
39
+ rejection: "(?:do not|don'?t|reject|rejected|cancel|no(?!tes?|thing|pe)\\b|\\bno\\b)",
40
+ question: "[?]|why|whether|can (?:you|i)|could (?:you|i)|should (?:you|i)|how|what about|is it|are there",
41
+ status_signal: "(?:^|\\s)(?:done|restarted|finished|entered|completed|ready|input done)(?:\\s|$)",
42
+ dangerous_action: "(?:remove-item|rm\\s+-r|del\\s+|overwrite|drop\\s+table)"
43
+ };
44
+
45
+ let lexiconsOverride = null; // null = 内置默认;非 null = 按键完全替换(Override)
46
+ let reCache = new Map(); // "key\0source" -> RegExp(setLexicons 时清空)
47
+
48
+ function compileRe(source, flags) {
49
+ if (typeof source !== "string" || source.length === 0) return null;
50
+ try {
51
+ return new RegExp(source, flags);
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /** 解析单个键的生效 source(个人层优先 派生键 → 内置默认) */
58
+ function resolveSource(key) {
59
+ const ov = lexiconsOverride?.[key];
60
+ if (typeof ov === "string" && ov.length > 0) return ov;
61
+ if (key === "approval_exec") {
62
+ // 派生:许可词 [0,16 字符内] 执行语(与 0.5.11 内置组合语义一致)
63
+ return `${resolveSource("approval")}[^\\n]{0,16}${resolveSource("exec_follow")}`;
64
+ }
65
+ return BUILTIN_LEXICONS[key] ?? "";
66
+ }
67
+
68
+ /**
69
+ * 注入个人层词表(rule-engine.json 的 lexicons 键)。
70
+ *
71
+ * 语义边界(P8 小批 A):
72
+ * · cfg 为 undefined/null(配置里没这个键)→ **不改变当前状态**(不干预词表层)。
73
+ * 进程首次启动时模块态本就是内置默认,故"不干预" == "用内置默认";而这一条同时让
74
+ * 测试夹具(先注入、后加载 index.js)不被插件启动流程清掉。
75
+ * · cfg 为对象 → 配置即真相:按键完全替换(Override);`{}` = 无有效键 = 回退内置默认。
76
+ * 非法/未知键被拒绝并返回明细(不静默半套生效)。
77
+ * @returns {{applied: string[], rejected: Array<{key: string, reason: string}>, noop?: boolean}}
78
+ */
79
+ export function setLexicons(cfg) {
80
+ const applied = [];
81
+ const rejected = [];
82
+ if (cfg === undefined || cfg === null) {
83
+ return { applied, rejected, noop: true }; // 配置缺省:不干预
84
+ }
85
+ if (typeof cfg !== "object" || Array.isArray(cfg)) {
86
+ lexiconsOverride = null;
87
+ reCache = new Map();
88
+ return { applied, rejected };
89
+ }
90
+ const next = {};
91
+ for (const [k, v] of Object.entries(cfg)) {
92
+ if (!LEXICON_KEYS.includes(k)) {
93
+ rejected.push({ key: k, reason: "unknown-key" });
94
+ continue;
95
+ }
96
+ if (typeof v !== "string" || v.length === 0) {
97
+ rejected.push({ key: k, reason: "empty-or-not-string" });
98
+ continue;
99
+ }
100
+ if (!compileRe(v, "i")) {
101
+ rejected.push({ key: k, reason: "invalid-regex" });
102
+ continue;
103
+ }
104
+ next[k] = v;
105
+ applied.push(k);
106
+ }
107
+ lexiconsOverride = Object.keys(next).length > 0 ? next : null;
108
+ reCache = new Map();
109
+ return { applied, rejected };
110
+ }
111
+
112
+ /** 显式回退内置默认(测试用;生产环境改配置后由插件重载生效) */
113
+ export function resetLexicons() {
114
+ lexiconsOverride = null;
115
+ reCache = new Map();
116
+ }
117
+
118
+ /** 当前生效的词表快照(键 → source;供 /guard 展示与测试断言,只读) */
119
+ export function effectiveLexicons() {
120
+ const out = {};
121
+ for (const k of LEXICON_KEYS) out[k] = resolveSource(k);
122
+ return out;
123
+ }
124
+
125
+ /** 当前是否处于个人层覆盖状态(true = 至少一个键来自配置) */
126
+ export function hasLexiconOverride() {
127
+ return lexiconsOverride !== null;
128
+ }
129
+
130
+ /**
131
+ * 取当前生效正则(唯一取词入口;调用方禁止缓存返回对象)。
132
+ * 非法/空 source → 返回永不命中的占位正则(守卫无对象,自然静默)。
133
+ */
134
+ export function lexRe(key) {
135
+ const source = resolveSource(key);
136
+ const ck = `${key}\u0000${source}`;
137
+ let hit = reCache.get(ck);
138
+ if (!hit) {
139
+ hit = compileRe(source, "i") || /(?!)/;
140
+ reCache.set(ck, hit);
141
+ }
142
+ return hit;
143
+ }
144
+
145
+ // ── 语义命名访问器(调用方优先用这些,避免手写键名字符串)──
146
+ export const approvalWordsRe = () => lexRe("approval");
147
+ export const execFollowRe = () => lexRe("exec_follow");
148
+ export const approvalExecRe = () => lexRe("approval_exec");
149
+ export const planOnlyRe = () => lexRe("plan_only");
150
+ export const actionWordsRe = () => lexRe("action_words");
151
+ export const strongExecRe = () => lexRe("strong_exec");
152
+ export const directiveWordsRe = () => lexRe("directive");
153
+ export const rejectionWordsRe = () => lexRe("rejection");
154
+ export const questionWordsRe = () => lexRe("question");
155
+ export const statusSignalRe = () => lexRe("status_signal");
156
+ export const dangerousActionRe = () => lexRe("dangerous_action");
157
+
158
+ // ── ⑤ 豁免工具清单(规则 22 ③ "只读/展示/ask/todo_write 等豁免"明细)──
159
+ // 注:工具名清单不是行为词表(语言无关),不进 lexicons 配置层。
160
+ // 只读/展示/询问/产物类工具在"无执行分点"回合也可调用(引擎 tool-catalog 的 analysis/artifact 分类
161
+ // 为总开关;本清单 = 规则正文"等豁免"所指的可枚举集,与 tool-catalog 一致,此处集中注释成对)。
162
+ export const EXEMPT_TOOL_NAMES = new Set([
163
+ // 只读/查询
164
+ "read", "grep", "glob", "read_image",
165
+ // 视觉分析
166
+ "vision_describe", "vision_detect", "vision_ground", "vision_colors", "vision_ocr", "vision_bootstrap",
167
+ // 状态查询
168
+ "job_list", "job_output", "list_agents", "get_goal", "schedule_list",
169
+ "engram_recall", "engram_detail", "engram_model",
170
+ "esr_status", "esr_ready", "esr_model",
171
+ // 网络只读
172
+ "web_fetch", "web_search",
173
+ // 询问/展示/产物
174
+ "ask_user_question", "todo_write", "visualize", "report",
175
+ // inspect 只读
176
+ "cordis_inspect_list", "cordis_inspect_query", "cordis_inspect_self"
177
+ ]);
@@ -10,7 +10,7 @@ import { parseUserIntents, shouldDenyMutation } from "./intent.js";
10
10
  import { resolveRoute, callLlm } from "./llm-understander.js";
11
11
  import { audit } from "./audit.js";
12
12
  import { learnHotword } from "./hotwords.js";
13
- import { ACTION_WORDS_RE } from "./lexicon.js";
13
+ import { actionWordsRe } from "./lexicon.js";
14
14
 
15
15
  const LLM_PROMPT = [
16
16
  "你是 DSH 规则引擎的意图判定器。输出严格 JSON,不要输出其他内容:",
@@ -106,7 +106,7 @@ export async function enrichIntentWithLlm(ctx, state, sid, text, cfg) {
106
106
  };
107
107
  // B1(2026-08-29 方案甲):LLM 明确判 execute 且词表未命中 → 学动作词入热词表
108
108
  //(下次同词词表直接命中,不再依赖 LLM;学习失败静默,不影响主流程)
109
- if (verdict.hasExecute && verdict.actionWord && !ACTION_WORDS_RE.test(text)) {
109
+ if (verdict.hasExecute && verdict.actionWord && !actionWordsRe().test(text)) {
110
110
  learnHotword(verdict.actionWord);
111
111
  }
112
112
  const ms = Date.now() - started;
@@ -1,19 +1,30 @@
1
1
  // matcher.js - 情境匹配机。
2
2
  // 每轮只激活相关规则子集,供 /guard active 展示,也用于控制执行成本。
3
- import { DSH_KEYWORDS_RE } from "./patterns.js";
3
+ import {
4
+ activateApprovalRe,
5
+ activateBackupRe,
6
+ activateNetworkRe,
7
+ activatePromiseRe,
8
+ activateSkillRe,
9
+ activateSourceRe,
10
+ activateTimeRe,
11
+ dshKeywordsRe
12
+ } from "./patterns.js";
13
+ import { kindOf } from "./measure-kinds.js";
4
14
 
5
15
  export function activateForUserMessage(configs, userText) {
6
16
  const text = userText || "";
7
17
  return configs.filter((cfg) => {
8
- if (cfg.handler === "rule18-manual-first" && DSH_KEYWORDS_RE.test(text)) return true;
9
- if (cfg.handler === "rule12b-skill" && /技能|skill/i.test(text)) return true;
10
- if (cfg.handler === "rule12a-approval" && /执行|创建|删除|覆盖|移动|下载|提交|配置/i.test(text)) return true;
11
- if (cfg.handler === "rule12c-network" && /下载|网络|curl|境外|clash/i.test(text)) return true;
12
- if (cfg.handler === "rule13a-backup" && /删除|覆盖|迁移|备份/i.test(text)) return true;
13
- if (cfg.handler === "rule2-time" && /时间|今天|昨天|日期/i.test(text)) return true;
14
- if (cfg.handler === "rule7-promise" && /保证|肯定|承诺/i.test(text)) return true;
15
- if (cfg.handler === "rule5-source" && /引用|来源|URL|链接/i.test(text)) return true;
16
- if (cfg.handler === "rule11-language" && CJK_PRESENT.test(text)) return true;
18
+ const kind = kindOf(cfg.handler);
19
+ if (kind === "manual-first" && dshKeywordsRe().test(text)) return true;
20
+ if (kind === "skill-auth" && activateSkillRe().test(text)) return true;
21
+ if (kind === "approval" && activateApprovalRe().test(text)) return true;
22
+ if (kind === "network" && activateNetworkRe().test(text)) return true;
23
+ if (kind === "backup" && activateBackupRe().test(text)) return true;
24
+ if (kind === "time" && activateTimeRe().test(text)) return true;
25
+ if (kind === "promise" && activatePromiseRe().test(text)) return true;
26
+ if (kind === "source" && activateSourceRe().test(text)) return true;
27
+ if (kind === "language" && CJK_PRESENT.test(text)) return true;
17
28
  return false;
18
29
  });
19
30
  }
@@ -24,15 +35,16 @@ export function activateForToolCall(configs, toolName, args) {
24
35
  const name = String(toolName || "");
25
36
  const argText = JSON.stringify(args || {});
26
37
  return configs.filter((cfg) => {
27
- if (cfg.handler === "rule9-inline-bom" && (name === "pwsh" || name === "bash")) return true;
28
- if (cfg.handler === "rule1-retry") return true;
29
- if (cfg.handler === "rule18-manual-first") return true;
30
- if (cfg.handler === "rule13a-backup" && (name === "pwsh" || name === "bash" || name === "edit" || name === "write")) return true;
31
- if (cfg.handler === "rule12b-skill" && name === "skill") return true;
32
- if (cfg.handler === "rule12a-approval" || cfg.handler === "rule12d-sensitive") {
38
+ const kind = kindOf(cfg.handler);
39
+ if (kind === "inline-command" && (name === "pwsh" || name === "bash")) return true;
40
+ if (kind === "retry") return true;
41
+ if (kind === "manual-first") return true;
42
+ if (kind === "backup" && (name === "pwsh" || name === "bash" || name === "edit" || name === "write")) return true;
43
+ if (kind === "skill-auth" && name === "skill") return true;
44
+ if (kind === "approval" || kind === "sensitive") {
33
45
  if (name === "pwsh" || name === "bash" || name === "edit" || name === "write" || name === "ask_user_question") return true;
34
46
  }
35
- if (cfg.handler === "rule21-meta" && (name === "edit" || name === "write")) return true;
47
+ if (kind === "meta" && (name === "edit" || name === "write")) return true;
36
48
  if (argText) {
37
49
  const low = argText.toLowerCase();
38
50
  if (cfg.triggerKeywords.some((k) => low.includes(k.toLowerCase()))) return true;
@@ -0,0 +1,127 @@
1
+ // measure-kinds.js - 措施类型注册表(架构 v3 §三 L1:通用层,零规则号)
2
+ //
3
+ // 依据:reports/2026-09-10-引擎分层架构-通用层零规则内容-v1.md
4
+ // §三 L1「措施类型注册表(measure kinds):每型一个通用解释器,命名去规则号」;
5
+ // §八 P1「措施类型注册表 + 解释器骨架;guard-core/matcher 改 kind → interpreter;内部名去规则号」。
6
+ //
7
+ // 三层分工(P1):
8
+ // ① MEASURE_KINDS —— 通用层的 kind 清单(**零规则号**,随包发布);
9
+ // ② LEGACY_HANDLER_ALIASES —— 历史内部名(带规则号)→ kind 的**兼容映射**
10
+ // (P4 清淤时迁个人层配置;保留在此是为了不破坏既有配置与规则正文声明);
11
+ // ③ kindOf() —— 唯一归一入口,接受 旧名 / kind 名 / 未知名 三种输入。
12
+ //
13
+ // 兼容面(P1 行为等价的前提):本机 rule-engine.json 的 handlerDefaultMap/handlerOverrides、
14
+ // 规则正文 `<!-- handler: … -->`、以及测试夹具,**仍可写旧内部名**——经 kindOf 归一后
15
+ // 与 kind 名完全等价。新用户/新规则写 kind 名即可,无需知道任何规则号。
16
+ export const MEASURE_KINDS = {
17
+ retry: "连续失败重试闸(第 3 次拒绝)",
18
+ time: "时间表述取证",
19
+ source: "引用出处标注",
20
+ promise: "承诺保守",
21
+ "inline-command": "禁止内联命令",
22
+ language: "语言与表达",
23
+ approval: "敏感操作授权确认",
24
+ "skill-auth": "技能调用授权",
25
+ network: "下载与网络",
26
+ backup: "写入前备份",
27
+ "manual-first": "先查手册再动手",
28
+ meta: "元规则管理",
29
+ "intent-direct": "无执行分点直拦",
30
+ "runtime-verify": "交付验证证据",
31
+ assembly: "插件装配类型守卫",
32
+ "release-asset": "发布资产规范",
33
+ "mount-audit": "挂载唯一性审计",
34
+ sensitive: "敏感操作(注入面)"
35
+ };
36
+
37
+ /** 历史内部名 → kind(兼容层;键含规则号,P4 迁个人层配置) */
38
+ export const LEGACY_HANDLER_ALIASES = {
39
+ "rule1-retry": "retry",
40
+ "rule2-time": "time",
41
+ "rule5-source": "source",
42
+ "rule7-promise": "promise",
43
+ "rule9-inline-bom": "inline-command",
44
+ "rule11-language": "language",
45
+ "rule12a-approval": "approval",
46
+ "rule12b-skill": "skill-auth",
47
+ "rule12c-network": "network",
48
+ "rule12d-sensitive": "sensitive",
49
+ "rule13a-backup": "backup",
50
+ "rule18-manual-first": "manual-first",
51
+ "rule21-meta": "meta",
52
+ "rule22-7-direct": "intent-direct",
53
+ "rule23-runtime-verify": "runtime-verify",
54
+ "rule24-assembly-type": "assembly",
55
+ "rule26-release-asset": "release-asset",
56
+ "rule27-mount-audit": "mount-audit"
57
+ };
58
+
59
+ /**
60
+ * 归一为 kind 名。
61
+ * 旧内部名 → kind;kind 名 → 原样(幂等);未知名 → 原样(由覆盖自省提示未覆盖)。
62
+ * @param {string} name
63
+ * @returns {string}
64
+ */
65
+ export function kindOf(name) {
66
+ const s = String(name || "").trim();
67
+ if (!s) return "";
68
+ return LEGACY_HANDLER_ALIASES[s] || s;
69
+ }
70
+
71
+ /** 兼容旧调用名(understander 对外导出同义函数) */
72
+ export function normalizeHandlerName(name) {
73
+ return kindOf(name);
74
+ }
75
+
76
+ // ── P2(2026-09-10):个人层「措施声明」解析 + 校验(架构正本 §三 L2/L3)──
77
+
78
+ /** 规则正文内联声明:`<!-- handler: <kind> -->` 或 `<!-- handler: <kind> {json} -->`
79
+ * 设计(方案 A,用户 2026-09-10 拍板):**扩展既有 handler 声明**而非新增独立块——
80
+ * 向后兼容(旧声明零影响)、单点解析入口。正则捕获 `-->` 之前的**全部内容**,之后再拆分,
81
+ * 避免用嵌套正则直接匹配 JSON(`}` 截断风险)。 */
82
+ const HANDLER_DECL_RE = /<!--\s*handler\s*:\s*([\s\S]*?)-->/i;
83
+
84
+ /**
85
+ * 解析个人层措施声明。
86
+ * @param {string} body 规则正文
87
+ * @returns {null | {kind: string, params: object} | {error: string}}
88
+ * null = 无声明;{kind, params} = 解析成功;{error} = 声明非法(调用方须 fail-closed)
89
+ */
90
+ export function parseHandlerDecl(body) {
91
+ const m = String(body || "").match(HANDLER_DECL_RE);
92
+ if (!m) return null;
93
+ const raw = String(m[1] || "").trim();
94
+ if (!raw) return { error: "声明为空(`<!-- handler: -->`)" };
95
+ const km = /^([A-Za-z0-9][A-Za-z0-9-]*)\s*([\s\S]*)$/.exec(raw);
96
+ if (!km) return { error: `声明格式非法:${raw.slice(0, 60)}` };
97
+ const kind = kindOf(km[1]);
98
+ const rest = String(km[2] || "").trim();
99
+ let params = {};
100
+ if (rest) {
101
+ let parsed;
102
+ try {
103
+ parsed = JSON.parse(rest);
104
+ } catch {
105
+ return { error: `参数不是合法 JSON:${rest.slice(0, 60)}` };
106
+ }
107
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
108
+ return { error: `参数必须是 JSON 对象:${rest.slice(0, 60)}` };
109
+ }
110
+ params = parsed;
111
+ }
112
+ return { kind, params };
113
+ }
114
+
115
+ /**
116
+ * 校验措施声明(架构正本 §三 L3:**kind 未注册即拒绝并明确报错,fail-closed**)。
117
+ * @param {null | {kind: string} | {error: string}} decl parseHandlerDecl 的输出
118
+ * @returns {null | string} null = 通过(或无声明);字符串 = 明确错误原因
119
+ */
120
+ export function validateMeasure(decl) {
121
+ if (!decl) return null;
122
+ if (decl.error) return decl.error;
123
+ if (!Object.prototype.hasOwnProperty.call(MEASURE_KINDS, decl.kind)) {
124
+ return `kind 未注册:${decl.kind}(已注册:${Object.keys(MEASURE_KINDS).join(" / ")})`;
125
+ }
126
+ return null;
127
+ }