dsh-rule-engine 0.6.2 → 0.6.3
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 +161 -6
- package/lib/core/audit.js +21 -4
- package/lib/core/authorization.js +18 -29
- package/lib/core/config.js +4 -1
- package/lib/core/dualtrack-markers.js +61 -0
- package/lib/core/guard-core.js +96 -16
- 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 +18 -9
- package/lib/core/patterns.js +344 -46
- package/lib/core/quality-ledger.js +124 -0
- package/lib/core/state.js +39 -3
- package/lib/core/text-detect.js +126 -68
- package/lib/core/understander.js +9 -9
- package/lib/index.js +119 -6
- package/lib/messages.js +134 -0
- package/package.json +3 -2
- package/scripts/check-tool-coverage.mjs +3 -1
- package/scripts/dualtrack-check.mjs +335 -0
- package/scripts/dualtrack-whitelist.json +7 -0
- package/scripts/local-residue-scan.mjs +10 -4
- package/scripts/plugins.json +10 -0
- package/scripts/release-plugin.mjs +23 -5
- package/scripts/verify-all.mjs +4 -0
- package/scripts/verify-v474.mjs +8 -8
package/lib/core/intent.js
CHANGED
|
@@ -18,33 +18,30 @@
|
|
|
18
18
|
// 消灭“两套疑问词判定不一致”(如 authorization 的“影响吗”与 intent 的“怎么”)。
|
|
19
19
|
// - 新增 STATUS_SIGNAL_RE / isStatusSignal():状态信号分类(宽松版,危险词排除,
|
|
20
20
|
// 12A/13A 兜底),与“无用户消息回合”同等待遇。
|
|
21
|
-
// 0.5.11(用户拍板):词表唯一源 = lib/core/lexicon.js——下面 5
|
|
22
|
-
//
|
|
23
|
-
//
|
|
21
|
+
// 0.5.11(用户拍板):词表唯一源 = lib/core/lexicon.js——下面 5 张表全部从那里取,
|
|
22
|
+
// 本文件不再自持定义。
|
|
23
|
+
// P8 小批 A(2026-09-08):改为函数取词(questionWordsRe() 等)——配置层注入后立即生效;
|
|
24
|
+
// 旧导出名 QUESTION_RE / STATUS_SIGNAL_RE 已删除(正则常量会锁死内置值,是本次改造的根因)。
|
|
25
|
+
// 对外仍以函数形态提供:questionWordsRe / statusSignalRe(lexicon.js)。
|
|
24
26
|
|
|
25
27
|
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
questionWordsRe,
|
|
29
|
+
actionWordsRe,
|
|
30
|
+
strongExecRe,
|
|
31
|
+
statusSignalRe,
|
|
32
|
+
dangerousActionRe
|
|
31
33
|
} from "./lexicon.js";
|
|
32
34
|
import { hasHotwordAction } from "./hotwords.js";
|
|
33
35
|
|
|
34
36
|
export const TAG_RE = /【(执行|方案|问询|信息)】/;
|
|
35
37
|
|
|
36
|
-
// 统一疑问词表(唯一源:lexicon.js;此处再导出保持外部引用兼容)
|
|
37
|
-
export const QUESTION_RE = QUESTION_WORDS_RE;
|
|
38
|
-
|
|
39
38
|
const DEFER_RE = /待确认|确认后再|等我确认|先方案|先别|不要执行|之后再说|确认后|再确认|先确认|不要直接/;
|
|
40
39
|
// 2026-08-31(柱子 C:条件句识别——"推送完成后…/等 X 了…/待…后…"型分点):
|
|
41
40
|
// 条件句分点 = 依赖前序分点完成后的后续动作,**本条不产生任何授权/scope**(安全方向:宁拦不放)。
|
|
42
41
|
// 词面拒绝"推送/上传"等动作词被当作本条独立动作(3 号分点"推送完成后发地址"实证:引擎曾把
|
|
43
42
|
// "推送"当本条动作致 git 全局授权弥漫——分点失效根因之一)。
|
|
44
43
|
const CONDITIONAL_RE = /(?:完成|结束|做完|弄完|搞定|好了|恢复|重启?)\s*(?:了|之后|后|以后|再)|(?:等|待|等\s*[\u4e00-\u9fff]{0,6}?)\s*[\u4e00-\u9fff]{0,8}?(?:后|之后|了)\s*|(?:推送|上传|提交|发布|执行)\s*(?:完成|结束|完|好)\s*(?:了|之?后)?/;
|
|
45
|
-
const ACTION_RE = ACTION_WORDS_RE;
|
|
46
44
|
const PLAN_RE = /方案|建议|评估|分析|设计|规划|计划|给出|提供/;
|
|
47
|
-
const STRONG_EXEC_RE = STRONG_EXEC_LEX;
|
|
48
45
|
const NUMBERED_ITEM_RE = /^\s*(?:\(?(\d+)[\)、..)]\s*)/;
|
|
49
46
|
// A1(2026-08-28 阶段一):具体动作词(区别于"执行/开始/进行"等宽泛词)。
|
|
50
47
|
// 用途:hasAction+hasPlan 时区分"给出执行方案"(方案请求)与"将建议书转化到业务通告"(执行指令)。
|
|
@@ -67,12 +64,8 @@ const AT_ACTION_RE = /看|读|改|写|转|查|处理|提取|打开|展示|审阅
|
|
|
67
64
|
/**
|
|
68
65
|
* 状态信号:对先前指令的就绪/完成确认(“我已重启 / 已输入 / 重启完成 / 好了 / done”)。
|
|
69
66
|
* 语义 = 与“无用户消息回合”同等待遇:不触发规则 22 的“无执行分点”判定(规则 22⑩)。
|
|
70
|
-
* 唯一源:lexicon.js
|
|
67
|
+
* 唯一源:lexicon.js(P8 小批 A 起经 statusSignalRe() 取词,配置注入后生效)。
|
|
71
68
|
*/
|
|
72
|
-
export const STATUS_SIGNAL_RE = STATUS_SIGNAL_WORDS_RE;
|
|
73
|
-
|
|
74
|
-
// 危险动作词:命中则不当状态信号(防“我已删除了某文件”被当成就绪确认)
|
|
75
|
-
const DANGEROUS_ACTION_RE = DANGEROUS_ACTION_WORDS_RE;
|
|
76
69
|
|
|
77
70
|
/** DSH 在会话上下文里注入的系统文本(工作区指令/技能目录等),不是用户的真实消息 */
|
|
78
71
|
export function isInjectedContextMessage(text) {
|
|
@@ -128,7 +121,7 @@ export function isStatusSignal(text) {
|
|
|
128
121
|
//(“完成 A 和 C 两项”/“重启服务”),不是对先前指令的就绪确认;状态信号须带“已/我已完成”前缀
|
|
129
122
|
// 或“了/完成/完毕/好”后缀(根因:STATUS_SIGNAL_RE 的“裸词 + 可选后缀”分支把命令式误判为状态)。
|
|
130
123
|
if (/^(?:请|帮我)?\s*(?:先|接着|然后)?\s*(?:完成|重启|执行|搞定|做完|弄好)\s+\S/.test(t.trim())) return false;
|
|
131
|
-
return
|
|
124
|
+
return statusSignalRe().test(t) && !dangerousActionRe().test(t);
|
|
132
125
|
}
|
|
133
126
|
|
|
134
127
|
function normalizeTag(tag) {
|
|
@@ -214,12 +207,12 @@ function classifyClause(clause) {
|
|
|
214
207
|
const text = String(clause.raw || "").trim();
|
|
215
208
|
const tag = extractTag(text);
|
|
216
209
|
const tagType = normalizeTag(tag);
|
|
217
|
-
const hasQuestion =
|
|
210
|
+
const hasQuestion = questionWordsRe().test(text);
|
|
218
211
|
const hasDefer = DEFER_RE.test(text);
|
|
219
212
|
// B1(2026-08-29):词表命中 ∪ 热词命中(热词=LLM 兜底已确认过的动作词,见 hotwords.js)
|
|
220
|
-
const hasAction =
|
|
213
|
+
const hasAction = actionWordsRe().test(text) || hasHotwordAction(text);
|
|
221
214
|
const hasPlan = PLAN_RE.test(text);
|
|
222
|
-
const hasStrongExec =
|
|
215
|
+
const hasStrongExec = strongExecRe().test(text);
|
|
223
216
|
// A3(阶段一):@文件引用信号 + 处理动词 → 执行分点(用户用 @ 引用文件即"处理它")
|
|
224
217
|
const hasAtRef = AT_REF_RE.test(text) && AT_ACTION_RE.test(text);
|
|
225
218
|
|
package/lib/core/lang.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// lang.js - 首启语言探测(第三批第 1 波 §4)
|
|
2
|
+
//
|
|
3
|
+
// 设计(DSH 方案 §五③ 选项 (b)):引擎启动时探测规则集语言——
|
|
4
|
+
// 读 AGENTS.md(经调用方传入其解析结果或原文),含 CJK → zh-CN;否则 en。
|
|
5
|
+
// 结果只缓存在**内存**,每次启动重探,**不落盘**(无配置文件、无副作用)。
|
|
6
|
+
// 用途:报错/提示/自由面/盲区等面向用户的展示语言。
|
|
7
|
+
//
|
|
8
|
+
// 注意:本模块是**通用层**——它只判断"用什么语言展示",不携带任何语言内容。
|
|
9
|
+
// 文案本身由 messages 层/个人层提供(第 1 批清淤时落地)。
|
|
10
|
+
export const LANGS = ["zh-CN", "en"];
|
|
11
|
+
const DEFAULT_LANG = "en";
|
|
12
|
+
const CJK = /[\u4e00-\u9fff]/;
|
|
13
|
+
|
|
14
|
+
let current = DEFAULT_LANG;
|
|
15
|
+
let detectedFrom = "default";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 探测语言:传规则集文本(AGENTS.md 原文或解析结果拼接),含 CJK → zh-CN。
|
|
19
|
+
* @param {string} text 规则集文本
|
|
20
|
+
* @returns {string} 生效语言
|
|
21
|
+
*/
|
|
22
|
+
export function detectLang(text) {
|
|
23
|
+
const t = String(text || "");
|
|
24
|
+
if (!t.trim()) {
|
|
25
|
+
current = DEFAULT_LANG;
|
|
26
|
+
detectedFrom = "empty";
|
|
27
|
+
return current;
|
|
28
|
+
}
|
|
29
|
+
current = CJK.test(t) ? "zh-CN" : "en";
|
|
30
|
+
detectedFrom = "agents-md";
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 当前生效语言 */
|
|
35
|
+
export function getLang() {
|
|
36
|
+
return current;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 探测来源(诊断用:default | empty | agents-md | manual) */
|
|
40
|
+
export function getLangSource() {
|
|
41
|
+
return detectedFrom;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 显式设置(测试/用户覆盖用;不落盘) */
|
|
45
|
+
export function setLang(lang) {
|
|
46
|
+
if (!LANGS.includes(lang)) throw new Error(`Unknown language: ${lang} (supported: ${LANGS.join(" / ")})`);
|
|
47
|
+
current = lang;
|
|
48
|
+
detectedFrom = "manual";
|
|
49
|
+
return current;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 双语选择:pickLang(zh, en) → 按当前语言取 */
|
|
53
|
+
export function pickLang(zh, en) {
|
|
54
|
+
return current === "zh-CN" ? zh : en;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 重置为默认(测试用) */
|
|
58
|
+
export function resetLang() {
|
|
59
|
+
current = DEFAULT_LANG;
|
|
60
|
+
detectedFrom = "default";
|
|
61
|
+
}
|
package/lib/core/lexicon.js
CHANGED
|
@@ -1,72 +1,177 @@
|
|
|
1
|
-
// lexicon.js -
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
+
]);
|
package/lib/core/llm-intent.js
CHANGED
|
@@ -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 {
|
|
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 && !
|
|
109
|
+
if (verdict.hasExecute && verdict.actionWord && !actionWordsRe().test(text)) {
|
|
110
110
|
learnHotword(verdict.actionWord);
|
|
111
111
|
}
|
|
112
112
|
const ms = Date.now() - started;
|
package/lib/core/matcher.js
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
// matcher.js - 情境匹配机。
|
|
2
2
|
// 每轮只激活相关规则子集,供 /guard active 展示,也用于控制执行成本。
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
activateApprovalRe,
|
|
5
|
+
activateBackupRe,
|
|
6
|
+
activateNetworkRe,
|
|
7
|
+
activatePromiseRe,
|
|
8
|
+
activateSkillRe,
|
|
9
|
+
activateSourceRe,
|
|
10
|
+
activateTimeRe,
|
|
11
|
+
dshKeywordsRe
|
|
12
|
+
} from "./patterns.js";
|
|
4
13
|
|
|
5
14
|
export function activateForUserMessage(configs, userText) {
|
|
6
15
|
const text = userText || "";
|
|
7
16
|
return configs.filter((cfg) => {
|
|
8
|
-
if (cfg.handler === "rule18-manual-first" &&
|
|
9
|
-
if (cfg.handler === "rule12b-skill" &&
|
|
10
|
-
if (cfg.handler === "rule12a-approval" &&
|
|
11
|
-
if (cfg.handler === "rule12c-network" &&
|
|
12
|
-
if (cfg.handler === "rule13a-backup" &&
|
|
13
|
-
if (cfg.handler === "rule2-time" &&
|
|
14
|
-
if (cfg.handler === "rule7-promise" &&
|
|
15
|
-
if (cfg.handler === "rule5-source" &&
|
|
17
|
+
if (cfg.handler === "rule18-manual-first" && dshKeywordsRe().test(text)) return true;
|
|
18
|
+
if (cfg.handler === "rule12b-skill" && activateSkillRe().test(text)) return true;
|
|
19
|
+
if (cfg.handler === "rule12a-approval" && activateApprovalRe().test(text)) return true;
|
|
20
|
+
if (cfg.handler === "rule12c-network" && activateNetworkRe().test(text)) return true;
|
|
21
|
+
if (cfg.handler === "rule13a-backup" && activateBackupRe().test(text)) return true;
|
|
22
|
+
if (cfg.handler === "rule2-time" && activateTimeRe().test(text)) return true;
|
|
23
|
+
if (cfg.handler === "rule7-promise" && activatePromiseRe().test(text)) return true;
|
|
24
|
+
if (cfg.handler === "rule5-source" && activateSourceRe().test(text)) return true;
|
|
16
25
|
if (cfg.handler === "rule11-language" && CJK_PRESENT.test(text)) return true;
|
|
17
26
|
return false;
|
|
18
27
|
});
|