dsh-rule-engine 0.5.12 → 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.
@@ -388,6 +388,9 @@ export function isAnalysisOp(toolName, args) {
388
388
  }
389
389
  }
390
390
  if (name === "node" || name === "pwsh" || name === "bash") {
391
+ // ㊄ 验证通道(2026-08-31,用户定稿):验证类命令(测试/体检/探针/审计/dry-run/只读查询)
392
+ // 独立放行——调研/验证灵活性与"执行类"严格授权分层;清单见 isVerificationCommand(执行类不匹配)。
393
+ if (isVerificationCommand(cmd)) return true;
391
394
  // ③ 临时区写:写命令(不含删除/移动——删除/移动属 13A 红线,不走分析豁免)+ 提取路径全部命中临时区 + 无受保护名
392
395
  if (isMutationCommand(cmd) && !/(?:Remove-Item|Move-Item|Rename-Item|del\s|rm\s|git\s+push|git\s+commit)/i.test(cmd)) {
393
396
  const paths = extractAnalysisScratchPaths(cmd);
@@ -576,11 +579,21 @@ export function isAuditCommand(command) {
576
579
  * v0.5.7 P0-1(用户拍板 2026-08-26):验证类命令判定——运行测试/冷加载探针/挂载审计/语法检查。
577
580
  * 与"修/改/写"授权伴生放行使用:用户授权修改后,验证是变更的正常闭环,不再被
578
581
  * write vs command 类型不匹配误拦(今天实弹连卡 4 次)。
582
+ * 2026-08-31(验证通道放行面,用户定稿):清单扩展 + 由 isAnalysisOp 独立调用——
583
+ * ① 单测通配(任意 *.test.mjs)、② 验证/门禁脚本(check-tool-coverage / publish-aptitude-check)、
584
+ * ③ --dry-run(发布脚本 dry-run=只读推导,发布链无 dry-run 不匹配)、④ gh api 只读 GET、npm whoami。
585
+ * 安全边界:执行类脚本(release-plugin 无 --dry-run / inject / uninject / install / publish / commit /
586
+ * push / Remove-Item 等)一律不匹配——执行类仍走授权语义严格版(0.5.11 防借道回归不放松)。
579
587
  */
580
588
  export function isVerificationCommand(command) {
581
589
  if (typeof command !== "string") return false;
582
- if (/\b(?:node|npm|npx)\s+[^\n]*(?:run-all\.mjs|check-plugin-load\.mjs|audit-mount-consistency\.mjs|loader-smoke\.e2e\.mjs|verify-all\.mjs|health-audit\.mjs|--check|--test)\b/i.test(command)) return true;
590
+ if (/\b(?:node|npm|npx)\s+[^\n]*(?:run-all\.mjs|check-plugin-load\.mjs|audit-mount-consistency\.mjs|loader-smoke\.e2e\.mjs|verify-all\.mjs|health-audit\.mjs|check-tool-coverage\.mjs|publish-aptitude-check\.mjs|\.test\.mjs\b|--check|--test|--dry-run)\b/i.test(command)) return true;
583
591
  if (/\b(?:npm|pnpm)\s+(?:test|run\s+test)\b/i.test(command)) return true;
592
+ if (/\bgh\s+api\b/i.test(command)) {
593
+ // 只读 GET 形态;写形态(--method/-X 显式方法、-F/--field 表单)不豁免(gg api 写=执行类)
594
+ return !/(?:--method|-X)\s+(?:POST|PUT|PATCH|DELETE)|\s-(?:F|f)\s|--field\s/i.test(command);
595
+ }
596
+ if (/\bnpm\s+whoami\b/i.test(command)) return true;
584
597
  return false;
585
598
  }
586
599
 
package/lib/core/state.js CHANGED
@@ -61,7 +61,13 @@ export function createState() {
61
61
  deniedKeys: new Set(), // 引擎 deny 的调用 key(规则 1 计数解耦)
62
62
  unknownToolApproved: new Set(), // 机制 B:已 ask 过的未归类工具(本会话内批准后不再重复询问)
63
63
  unknownToolApprovedMeta: new Map(), // v0.5.9:name → {name,time,session}(持久化元数据,/guard tools 可视化)
64
- unknownToolSessionAdded: new Set() // v0.5.9:本会话新增的批准(与永久加载区分,/guard tools 展示)
64
+ unknownToolSessionAdded: new Set(), // v0.5.9:本会话新增的批准(与永久加载区分,/guard tools 展示)
65
+ // T3 通用化(2026-08-31):声明式 handler 绑定覆盖表 { ruleId: handlerName }
66
+ // 由 apply() 从插件配置(rule-engine.json 可选键 handlerOverrides)注入;重载时经理解层生效
67
+ handlerOverrides: {},
68
+ // 残余1 剥离(2026-08-31):本机默认偏好表(rule-engine.json 可选键 handlerDefaultMap)
69
+ // 由 apply() 注入;重载时经理解层生效(通用部署=空表,零本机编号)
70
+ handlerDefaultMap: {}
65
71
  };
66
72
  }
67
73
 
@@ -268,28 +274,40 @@ export function resetTurn(state, sessionId, turnNumber) {
268
274
  return s.turn;
269
275
  }
270
276
 
271
- /** 读取 dsh-rules-manager 的 disabled-rules.json,返回已禁用规则 id 集合(P0-2) */
272
- export function loadDisabledRuleIds() {
277
+ /** 读取 dsh-rules-manager 的 disabled-rules.json 全部存档条目(禁用=正文移走+原样存档) */
278
+ export function loadDisabledRuleEntries() {
273
279
  try {
274
280
  const raw = readFileSync(disabledRulesFilePath(), "utf8");
275
281
  const list = JSON.parse(raw);
276
- if (!Array.isArray(list)) return new Set();
277
- return new Set(list.map((d) => String(d?.index)).filter(Boolean));
282
+ if (!Array.isArray(list)) return [];
283
+ return list.filter((d) => d && d.index !== undefined && d.index !== null);
278
284
  } catch {
279
- return new Set();
285
+ return [];
280
286
  }
281
287
  }
282
288
 
283
- /** 重新加载并理解 AGENTS.md;统一在 reload 后刷新理解产物(P0-3) */
289
+ /** 已禁用规则 id 集合(P0-2;加载失败=空集,保守不误拦) */
290
+ export function loadDisabledRuleIds() {
291
+ return new Set(loadDisabledRuleEntries().map((d) => String(d.index)).filter(Boolean));
292
+ }
293
+
294
+ /**
295
+ * 重新加载并理解 AGENTS.md;统一在 reload 后刷新理解产物(P0-3)。
296
+ * 禁用语义(2026-08-31 修复):禁用规则以 disabled=true 占位存在——不只是标记"在场规则"。
297
+ * 正文已移走的禁用规则由 understandAll(opts.disabledEntries)用存档正文重建占位:
298
+ * ① 禁用不再"消失"(/guard rules 显示(已禁用),恢复=写回 AGENTS.md 后自动重新生效);
299
+ * ② 死映射消除(HANDLER_BY_RULE 中的 12B 等因规则在场而不再被判 dead,consistency-live 转绿)。
300
+ */
284
301
  export function reloadRules(state) {
285
302
  const parsed = loadRules();
286
303
  state.rules = parsed.ok ? parsed.rules : [];
287
- state.configs = understandAll(state.rules);
288
- // 对接 disabled-rules.json:被禁用规则在引擎侧同样标记 disabled(不参与硬拦/纠察)
289
- const disabledIds = loadDisabledRuleIds();
290
- for (const cfg of state.configs) {
291
- if (disabledIds.has(String(cfg.ruleId))) cfg.disabled = true;
292
- }
304
+ const archived = loadDisabledRuleEntries();
305
+ // 禁用语义由理解层完整封装:在场标 disabled + 缺场存档占位重建(单真源,见 understandAll)
306
+ state.configs = understandAll(state.rules, {
307
+ disabledEntries: archived,
308
+ handlerOverrides: state.handlerOverrides || {},
309
+ defaultMap: state.handlerDefaultMap || {}
310
+ });
293
311
  state.configOk = parsed.ok;
294
312
  state.configError = parsed.error || null;
295
313
  state.mtimeMs = parsed.mtimeMs || 0;
@@ -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
- const HANDLER_BY_RULE = {
18
- 1: "rule1-retry",
19
- 2: "rule2-time",
20
- 5: "rule5-source",
21
- 7: "rule7-promise",
22
- 9: "rule9-inline-bom",
23
- 11: "rule11-language",
24
- "12A": "rule12a-approval",
25
- "12B": "rule12b-skill",
26
- "12C": "rule12c-network",
27
- "13A": "rule13a-backup",
28
- // 13B 已于 2026-08-24 外移至手册(规则正文删除,映射一并清理,防死映射)
29
- // 14 为纯 D 级自证规则,无对应 handler(0.5.11:删除空转映射 registry)
30
- 18: "rule18-manual-first",
31
- 21: "rule21-meta",
32
- 22: "rule22-7-direct",
33
- 23: "rule23-runtime-verify",
34
- 24: "rule24-assembly-type",
35
- 26: "rule26-release-asset",
36
- 27: "rule27-mount-audit"
37
- };
38
-
39
- /**
40
- * 覆盖自省(一致性预防机制):
41
- * - dead:HANDLER_BY_RULE 映射了但 AGENTS.md 无此规则 ID(死映射,应清理)
42
- * - uncovered:A/C/M(deny/ask/meta)级但无 handler 的规则(规则声称会被机器执行,实际不会)
43
- * /guard status 与一致性测试/脚本中使用;规则 21④/19⑦ 的体检触发条件之一。
44
- */
45
- export function analyzeCoverage(configs) {
46
- const dead = new Map(); // ruleId -> handler(映射存在但规则不存在)
47
- const uncovered = []; // A/C/M 级规则但引擎无执行 handler
48
- const present = new Set();
49
- for (const cfg of configs || []) {
50
- if (cfg && cfg.ruleId !== undefined && cfg.ruleId !== null) present.add(String(cfg.ruleId));
51
- }
52
- for (const [id, handler] of Object.entries(HANDLER_BY_RULE)) {
53
- if (!present.has(String(id))) dead.set(String(id), handler);
54
- }
55
- for (const cfg of configs || []) {
56
- if (cfg.disabled) continue;
57
- const acts = cfg.actions || [];
58
- const hard = acts.some((a) => a === "deny" || a === "ask" || a === "meta");
59
- if (hard && !cfg.handler) {
60
- uncovered.push({ ruleId: String(cfg.ruleId), title: cfg.title, actions: acts });
61
- }
62
- }
63
- return { dead, uncovered };
64
- }
65
-
66
- /** 根据执行等级推导动作(容忍空白/强弱/连接符:如 "B + D"、"A 弱 + D"、"D 强") */
67
- export function actionsForLevel(level) {
68
- const s = String(level || "").toUpperCase().replace(/\s+/g, "");
69
- const actions = [];
70
- if (s.includes("A")) actions.push("deny");
71
- if (s.includes("B")) actions.push("correct");
72
- if (s.includes("C")) actions.push("ask");
73
- if (s.includes("D")) actions.push("self-certify");
74
- if (s.includes("M")) actions.push("meta");
75
- if (actions.length === 0) actions.push("self-certify");
76
- return [...new Set(actions)];
77
- }
78
-
79
- function splitKeywords(text) {
80
- if (!text) return [];
81
- return text
82
- .split(/[,。;、,\n;::/\\()()]+/)
83
- .map((s) => s.trim())
84
- .filter((s) => s.length >= 2 && s.length <= 20);
85
- }
86
-
87
- /** 从检查文本提取机器可用的提示词 */
88
- function hintPatterns(checkText) {
89
- const hints = [];
90
- if (/node\s+-e|node\s+-p|pwsh\s+-c|--eval|--print|-Command\b/i.test(checkText)) hints.push("inline-command");
91
- if (/set-content|out-file|add-content|writealltext|utf8bom/i.test(checkText)) hints.push("bom-write");
92
- if (/ask_user_question|授权|弹框/i.test(checkText)) hints.push("ask");
93
- if (/get-date|时间词|昨天|今天/i.test(checkText)) hints.push("time");
94
- if (/skill|技能/i.test(checkText)) hints.push("skill");
95
- if (/git\s+(push|commit)|敏感操作|授权/i.test(checkText)) hints.push("sensitive");
96
- if (/删除|覆盖|备份|验证/i.test(checkText)) hints.push("backup");
97
- if (/手册/i.test(checkText)) hints.push("manual");
98
- if (/重试|连续失败|第\s*3\s*次/i.test(checkText)) hints.push("retry");
99
- if (/运行时验证|mock|启动|实测/i.test(checkText)) hints.push("runtime-verify");
100
- if (/URL|来源|出处|引用/i.test(checkText)) hints.push("source");
101
- if (/中文|英文|语言/i.test(checkText)) hints.push("language");
102
- return [...new Set(hints)];
103
- }
104
-
105
- /**
106
- * 理解一条规则。
107
- * @param {object} rule parser 输出
108
- * @returns {object} 执行配置
109
- */
110
- export function understandRule(rule) {
111
- const elems = extractElements(rule.body || "");
112
- const level = rule.level || "";
113
- const actions = actionsForLevel(level);
114
- const hints = hintPatterns(elems.check);
115
- const triggerKeywords = splitKeywords(elems.trigger);
116
- const confidence = level && elems.trigger && elems.check && (elems.action || level.toUpperCase().includes("D")) ? "high" : level ? "medium" : "low";
117
- const handler = HANDLER_BY_RULE[rule.index] || "";
118
- const config = {
119
- ruleId: rule.index,
120
- title: rule.title,
121
- section: rule.section,
122
- level,
123
- actions,
124
- confidence,
125
- handler,
126
- triggerKeywords,
127
- hints,
128
- elements: elems,
129
- disabled: false
130
- };
131
- return config;
132
- }
133
-
134
- /** 批量理解 */
135
- export function understandAll(rules) {
136
- return rules.map(understandRule);
137
- }
138
-
139
- /** 导出常用正则供测试/调试 */
140
- export const REGEX = {
141
- INLINE_CMD,
142
- BOM_WRITE,
143
- DESTRUCTIVE_CMD,
144
- SENSITIVE_CMD,
145
- TIME_WORDS,
146
- PROMISE_WORDS,
147
- URL_RE,
148
- SOURCE_MARK,
149
- DSH_KEYWORDS_RE
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
+ };