dsh-rule-engine 0.5.11 → 0.5.12

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.
@@ -17,10 +17,38 @@ export function defaultContract() {
17
17
  hashPolicy: "deny",
18
18
  allowedPaths: null,
19
19
  dependencyPolicy: "ask",
20
+ // 0.5.12(F5 批次 3):类别白名单——null=不限制类别(走既有判定);数组=仅放行该类别集合。
21
+ // 破坏类(delete/move/rm 等)永不可入 categories(由 isDestructiveCategory 结构性排除)。
22
+ categories: null,
23
+ // 0.5.12(F5 批次 3):完成自动降级——契约内命令成功后(exit 0 + 产出物),将此信号记录
24
+ // 至 completesAfter,后续契约命中自动降级为普通判定(TTL 不再续期)。
25
+ completesAfter: null,
20
26
  source: "default"
21
27
  };
22
28
  }
23
29
 
30
+ /** 非破坏动作类别(F5 类别白名单的可选集;破坏类永不可入) */
31
+ export const NON_DESTRUCTIVE_CATEGORIES = new Set(["install", "build", "test", "audit", "analyze", "naming", "sync", "restore"]);
32
+ /** 破坏类类别(任何契约类别白名单都必须排除) */
33
+ export const DESTRUCTIVE_CATEGORIES = new Set(["delete", "move", "replace", "purge"]);
34
+
35
+ /** 从命令文本推断动作类别(纯函数;未知→null 走普通判定)。破坏性词返回破坏类。 */
36
+ export function categoryOfCommand(command) {
37
+ const s = String(command || "").toLowerCase();
38
+ if (/remove-item|\brm(?:dir)?\b|del(?:ete)?\b|move-item|\bmv\b|rename-item|\bren\b|clear-content|clean|purge|rm\s+-/.test(s)) return "delete";
39
+ if (/install|add\s+--save|pnpm\s+add|npm\s+install|yarn\s+add/.test(s)) return "install";
40
+ if (/build|tsc|tsdown|vite\s+build|webpack|rollup|compile|bundle/.test(s)) return "build";
41
+ if (/test|jest|vitest|playwright|npm\s+test|pnpm\s+test|check-plugin-load|verify/.test(s)) return "test";
42
+ if (/audit|mount|consistency|dump-config|inspect/.test(s)) return "audit";
43
+ if (/analyze|analysis|review|read|grep|search/.test(s)) return "analyze";
44
+ return null;
45
+ }
46
+
47
+ /** 类别是否属于破坏类(结构性排除:破坏类永不因契约类别白名单放行) */
48
+ export function isDestructiveCategory(cat) {
49
+ return cat === "delete" || cat === "move" || cat === "replace" || cat === "purge";
50
+ }
51
+
24
52
  /**
25
53
  * 会话契约初始化(批次 4,阶段 3 默认武装):
26
54
  * 优先级:defaults 显式(用户配置)> global 模板(用户 /guard mode|budget 写入且非默认)> armed 推导 > 系统默认。
@@ -270,6 +298,19 @@ export function decideContractAction({ contract, action, config = {} }) {
270
298
  return { outcome: "deny", family: "S", reasonCode: "DEPENDENCY_NOT_AUTHORIZED", reason: "检测到添加依赖操作,当前 deps=deny", nextStep: "使用 deps=allow 或说明必要性" };
271
299
  }
272
300
 
301
+ // 0.5.12(F5 批次 3):类别白名单——契约配置了 categories 时:
302
+ // ① 破坏类句子结构性拒绝(不因契约放行——最坏情况缓解);
303
+ // ② 已知类别不在白名单 → deny;未知类别(null)→ 普通判定(不放大契约)。
304
+ if (Array.isArray(contract.categories) && contract.categories.length > 0) {
305
+ const cat = categoryOfCommand(action.commandText || action.cmd || "");
306
+ if (cat && isDestructiveCategory(cat)) {
307
+ return { outcome: "deny", family: "S", reasonCode: "DESTRUCTIVE_NOT_ALLOWED", reason: `动作类别 ${cat} 属破坏类,任务契约永不放行`, nextStep: "改用非破坏操作,或退出任务契约" };
308
+ }
309
+ if (cat && !contract.categories.includes(cat)) {
310
+ return { outcome: "deny", family: "S", reasonCode: "CATEGORY_NOT_IN_CONTRACT", reason: `动作类别 ${cat} 不在契约白名单 [${contract.categories.join(",")}]`, nextStep: `更新契约类别(如 /guard contract categories build,test)` };
311
+ }
312
+ }
313
+
273
314
  if (action.mutability === "delegate" && action.unboundedDelegation) {
274
315
  return { outcome: "deny", family: "S", reasonCode: "UNBOUNDED_DELEGATION", reason: "该委托可能无界启动子代理,无法满足 agents=N", nextStep: "使用显式子代理调用,或关闭任务契约" };
275
316
  }