pi-verdict 0.2.0 → 0.2.1
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 +11 -2
- package/README.zh-CN.md +11 -2
- package/extensions/auto-mode.ts +66 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
| [@zhushanwen/pi-permission](https://www.npmjs.com/package/@zhushanwen/pi-permission) | ✅ (outcome) | ❌ single-turn, no context | closed (→ ask) | 4 |
|
|
22
22
|
| [@gotgenes/pi-permission-system](https://github.com/gotgenes/pi-packages) | ✅ deterministic only | — (no built-in classifier) | closed | 3 |
|
|
23
23
|
|
|
24
|
-
Full landscape: [`research/pi-permission-landscape.md`](research/pi-permission-landscape.md)
|
|
24
|
+
Full landscape: [`research/pi-permission-landscape.md`](research/pi-permission-landscape.md) · convergence analysis with the closest architectural relative: [`research/pi-automode-convergence.md`](research/pi-automode-convergence.md).
|
|
25
|
+
|
|
26
|
+
Honest framing: pi-automode and pi-verdict have **converged on the same architecture** (deny floor → user rules → classifier, fail-closed — see the convergence analysis). What remains distinct here: a classifier that can say `ask` (runtime human-in-the-loop, not just rule-declared), a built-in floor you can turn off (`builtinDenyFloor` — user sovereignty), a zero-dependency single file (~700 lines, deliberate), and the measurement habit — every design decision in this repo is backed by shipped research.
|
|
27
|
+
|
|
28
|
+
The single-file, zero-dependency shape is deliberate — the whole extension is one readable [~700-line file](extensions/auto-mode.ts).
|
|
25
29
|
|
|
26
30
|
## Pipeline
|
|
27
31
|
|
|
@@ -84,13 +88,16 @@ cp extensions/auto-mode.ts ~/.pi/agent/extensions/
|
|
|
84
88
|
{
|
|
85
89
|
"allow": ["^ls\\b", "^git (status|log|diff)\\b"],
|
|
86
90
|
"deny": ["rm ", "docker ", "^/etc/"],
|
|
87
|
-
"builtinDenyFloor": true
|
|
91
|
+
"builtinDenyFloor": true,
|
|
92
|
+
"classifierModel": null
|
|
88
93
|
}
|
|
89
94
|
```
|
|
90
95
|
|
|
91
96
|
- `allow`/`deny` are JS regex arrays; **`deny` wins over `allow`**, both beat the classifier
|
|
92
97
|
- matched against the **full command string** for bash, the **absolute path** for file tools (read/write/edit/grep/find/ls); other tools (MCP etc.) always go to the classifier
|
|
93
98
|
- `builtinDenyFloor: false` turns the built-in danger/path floor off entirely (risk accepted by you; the classifier and your rules remain)
|
|
99
|
+
- `classifierModel: "provider/model-id"` persistently sets the classifier model (e.g. a fast flash-class model); precedence is flag > env > config > session model (self-reflection); an invalid value falls back to the session model with a one-time warning
|
|
100
|
+
- the spec accepts pi's native `--model` thinking suffix: `"zai/glm-4-flash:low"` sets classifier thinking to effort low (default without suffix: thinking explicitly off — the [measured](research/thinking-param-blackhole.md) default)
|
|
94
101
|
- first run generates a template at `~/.pi/agent/config/pi-verdict.json` (honors `PI_CODING_AGENT_DIR`); changes apply to new sessions
|
|
95
102
|
|
|
96
103
|
Why no built-in allowlist? A third-party security audit ([`research/rule-layer-security-audit.md`](research/rule-layer-security-audit.md)) showed that allowlist soundness requires shell AST analysis — every built-in "always allow" would be a security claim maintained by the author. The built-in layer only makes **deny** claims (the sound direction); allow claims are yours.
|
|
@@ -105,6 +112,8 @@ Design decisions here are settled by measurement, and the lab notes ship with th
|
|
|
105
112
|
- [`research/thinking-param-blackhole.md`](research/thinking-param-blackhole.md) — three-layer forensic root-cause of thinking models burning the classifier budget; why the fix is `thinkingEnabled: false`
|
|
106
113
|
- [`research/rule-engine-sim`](research/rule-engine-sim/README.md) — measured a tree-sitter rule-engine port against 746 real bash calls (**absorbs 0 gray calls**) and rejected it
|
|
107
114
|
- [`research/pi-permission-landscape.md`](research/pi-permission-landscape.md) — the competitive landscape this README's positioning is checked against
|
|
115
|
+
- [`research/rule-layer-security-audit.md`](research/rule-layer-security-audit.md) — third-party audit of the rule layer (8/8 reproduced → fixed architecturally in 0.2.0)
|
|
116
|
+
- [`research/pi-automode-convergence.md`](research/pi-automode-convergence.md) — where this project genuinely converges with pi-automode, and what remains distinct
|
|
108
117
|
- [`research/claude-code-classifier-prompts.md`](research/claude-code-classifier-prompts.md) — structural reconstruction of Claude Code's classifier design (via self-hosted Langfuse observations) that this extension's transcript contract descends from
|
|
109
118
|
|
|
110
119
|
## Status & limitations
|
package/README.zh-CN.md
CHANGED
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
| [@zhushanwen/pi-permission](https://www.npmjs.com/package/@zhushanwen/pi-permission) | ✅(outcome) | ❌ 单轮无上下文 | closed(→ ask) | 4 |
|
|
22
22
|
| [@gotgenes/pi-permission-system](https://github.com/gotgenes/pi-packages) | ✅ 纯确定性 | —(无内置分类器) | closed | 3 |
|
|
23
23
|
|
|
24
|
-
完整全景:[`research/pi-permission-landscape.md`](research/pi-permission-landscape.md)
|
|
24
|
+
完整全景:[`research/pi-permission-landscape.md`](research/pi-permission-landscape.md) · 与最近架构亲缘的收敛分析:[`research/pi-automode-convergence.md`](research/pi-automode-convergence.md)。
|
|
25
|
+
|
|
26
|
+
诚实地说:pi-automode 与 pi-verdict 在**架构上已收敛**(deny floor → 用户规则 → 分类器,fail-closed——见收敛分析)。这里仍然不同的是:分类器能说 `ask`(运行时人工介入,而非仅由规则预声明)、内置 floor 可以关(`builtinDenyFloor`——用户主权)、零依赖单文件(~700 行,刻意为之)、以及测量的习惯——本仓库每个设计决策都有随库研究背书。
|
|
27
|
+
|
|
28
|
+
零依赖单文件形态是有意为之——整个扩展就是一个可通读的 [~700 行文件](extensions/auto-mode.ts)。
|
|
25
29
|
|
|
26
30
|
## 管线
|
|
27
31
|
|
|
@@ -83,13 +87,16 @@ cp extensions/auto-mode.ts ~/.pi/agent/extensions/
|
|
|
83
87
|
{
|
|
84
88
|
"allow": ["^ls\\b", "^git (status|log|diff)\\b"],
|
|
85
89
|
"deny": ["rm ", "docker ", "^/etc/"],
|
|
86
|
-
"builtinDenyFloor": true
|
|
90
|
+
"builtinDenyFloor": true,
|
|
91
|
+
"classifierModel": null
|
|
87
92
|
}
|
|
88
93
|
```
|
|
89
94
|
|
|
90
95
|
- `allow`/`deny` 为 JS 正则数组;**`deny` 优先于 `allow`**,两者都优先于分类器
|
|
91
96
|
- 匹配目标:bash = **完整命令串**;文件类工具(read/write/edit/grep/find/ls)= **绝对路径**;其余工具(MCP 等)恒走分类器
|
|
92
97
|
- `builtinDenyFloor: false` 可整体关闭内置危险/路径拦截(风险自担;分类器与你的规则仍在)
|
|
98
|
+
- `classifierModel: "provider/model-id"` 持久指定分类器模型(如轻量 flash 类);优先级 flag > env > config > 自省;无效值回退会话模型并一次性警告
|
|
99
|
+
- spec 支持 pi 原生 `--model` 思考级别后缀:`"zai/glm-4-flash:low"` 将分类器思考设为 effort low(无后缀缺省 = 显式关思考,[实测](research/thinking-param-blackhole.md)背书的默认)
|
|
93
100
|
- 首次运行自动生成模板 `~/.pi/agent/config/pi-verdict.json`(尊重 `PI_CODING_AGENT_DIR`);修改后新会话生效
|
|
94
101
|
|
|
95
102
|
为什么没有内置白名单?第三方安全审计(见 [`research/rule-layer-security-audit.md`](research/rule-layer-security-audit.md))证明白名单的健全性需要 shell AST 分析——每条内置「永远放行」都是作者维护的安全声明。因此内置层只做 **deny** 声明(方向健全),allow 声明归你。
|
|
@@ -104,6 +111,8 @@ cp extensions/auto-mode.ts ~/.pi/agent/extensions/
|
|
|
104
111
|
- [`research/thinking-param-blackhole.md`](research/thinking-param-blackhole.md) —— 思考模型烧尽分类器预算的三层取证,以及为什么修复是 `thinkingEnabled: false`
|
|
105
112
|
- [`research/rule-engine-sim`](research/rule-engine-sim/README.md) —— 用 746 条真实 bash 调用实测 tree-sitter 规则引擎移植(**灰区吸收 0 条**)并否决
|
|
106
113
|
- [`research/pi-permission-landscape.md`](research/pi-permission-landscape.md) —— 本 README 定位所对照的竞品全景
|
|
114
|
+
- [`research/rule-layer-security-audit.md`](research/rule-layer-security-audit.md) —— 规则层第三方安全审计(8/8 复现 → 0.2.0 架构性修复)
|
|
115
|
+
- [`research/pi-automode-convergence.md`](research/pi-automode-convergence.md) —— 与 pi-automode 何处真正收敛、何处仍然不同
|
|
107
116
|
- [`research/claude-code-classifier-prompts.md`](research/claude-code-classifier-prompts.md) —— Claude Code 分类器设计的结构化还原(基于自托管 Langfuse 观测),本扩展 transcript 契约的血统来源
|
|
108
117
|
|
|
109
118
|
## 状态与限制
|
package/extensions/auto-mode.ts
CHANGED
|
@@ -23,11 +23,13 @@
|
|
|
23
23
|
*
|
|
24
24
|
* 配置:
|
|
25
25
|
* --auto-mode / --no-auto-mode CLI flag,总开关(默认开)
|
|
26
|
-
* --auto-mode-model provider/id 分类器模型
|
|
26
|
+
* --auto-mode-model provider/id[:thinking] 分类器模型 + 可选思考级别后缀
|
|
27
|
+
* (pi 原生 --model 语法;缺省 off = 显式关思考)
|
|
27
28
|
* PI_AUTO_MODE_MODEL 同上的环境变量形式
|
|
28
29
|
* --auto-mode-debug 所有裁决(含放行)都弹通知;影子缓存标注同步开启
|
|
29
30
|
* PI_AUTO_MODE_DEBUG=1 同上的环境变量形式(兼容保留)
|
|
30
|
-
* <agentDir>/config/pi-verdict.json 用户规则:{ allow: [regex], deny: [regex]
|
|
31
|
+
* <agentDir>/config/pi-verdict.json 用户规则:{ allow: [regex], deny: [regex],
|
|
32
|
+
* builtinDenyFloor, classifierModel }
|
|
31
33
|
* 匹配:bash=完整命令串 / 文件工具=绝对路径;新会话生效
|
|
32
34
|
*
|
|
33
35
|
* 已知原型简化(见 README「已知限制」):
|
|
@@ -101,9 +103,11 @@ interface UserRules {
|
|
|
101
103
|
deny: RegExp[];
|
|
102
104
|
/** 内置 deny floor 开关(危险正则 + 路径敏感度 deny),默认 true;关闭后依赖用户规则与分类器 */
|
|
103
105
|
builtinDenyFloor: boolean;
|
|
106
|
+
/** 分类器模型 spec(provider/id);null = 未配置(自省继承会话模型) */
|
|
107
|
+
classifierModel: string | null;
|
|
104
108
|
}
|
|
105
109
|
|
|
106
|
-
const EMPTY_RULES: UserRules = { allow: [], deny: [], builtinDenyFloor: true };
|
|
110
|
+
const EMPTY_RULES: UserRules = { allow: [], deny: [], builtinDenyFloor: true, classifierModel: null };
|
|
107
111
|
|
|
108
112
|
function userConfigPath(): string {
|
|
109
113
|
const agentDir = process.env.PI_CODING_AGENT_DIR ?? path.join(os.homedir(), ".pi", "agent");
|
|
@@ -111,10 +115,11 @@ function userConfigPath(): string {
|
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
const USER_CONFIG_TEMPLATE = `${JSON.stringify({
|
|
114
|
-
_hint: "pi-verdict 用户规则。allow/deny 为 JS 正则数组;deny 优先于 allow;匹配目标:bash=完整命令串,文件工具=绝对路径。builtinDenyFloor=false 可关闭内置危险规则/路径敏感度拦截(风险自担)。修改后新会话生效。",
|
|
118
|
+
_hint: "pi-verdict 用户规则。allow/deny 为 JS 正则数组;deny 优先于 allow;匹配目标:bash=完整命令串,文件工具=绝对路径。builtinDenyFloor=false 可关闭内置危险规则/路径敏感度拦截(风险自担)。classifierModel 可持久指定分类器模型(provider/id,如 zai/glm-4-flash;可带 pi 原生思考后缀如 zai/glm-4-flash:low;留空=自省继承会话模型)。修改后新会话生效。",
|
|
115
119
|
allow: ["^ls\\b"],
|
|
116
120
|
deny: [],
|
|
117
121
|
builtinDenyFloor: true,
|
|
122
|
+
classifierModel: null,
|
|
118
123
|
}, null, 2)}\n`;
|
|
119
124
|
|
|
120
125
|
/**
|
|
@@ -131,7 +136,7 @@ function loadUserRules(): { rules: UserRules; skipped: string[] } {
|
|
|
131
136
|
} catch { /* 只读环境静默跳过 */ }
|
|
132
137
|
return { rules: EMPTY_RULES, skipped: [] };
|
|
133
138
|
}
|
|
134
|
-
const raw = JSON.parse(fs.readFileSync(p, "utf8")) as { allow?: unknown; deny?: unknown; builtinDenyFloor?: unknown };
|
|
139
|
+
const raw = JSON.parse(fs.readFileSync(p, "utf8")) as { allow?: unknown; deny?: unknown; builtinDenyFloor?: unknown; classifierModel?: unknown };
|
|
135
140
|
const skipped: string[] = [];
|
|
136
141
|
const compile = (list: unknown): RegExp[] =>
|
|
137
142
|
(Array.isArray(list) ? list : []).filter((x): x is string => typeof x === "string").flatMap((src) => {
|
|
@@ -142,7 +147,15 @@ function loadUserRules(): { rules: UserRules; skipped: string[] } {
|
|
|
142
147
|
return [];
|
|
143
148
|
}
|
|
144
149
|
});
|
|
145
|
-
return {
|
|
150
|
+
return {
|
|
151
|
+
rules: {
|
|
152
|
+
allow: compile(raw.allow),
|
|
153
|
+
deny: compile(raw.deny),
|
|
154
|
+
builtinDenyFloor: raw.builtinDenyFloor !== false,
|
|
155
|
+
classifierModel: typeof raw.classifierModel === "string" && raw.classifierModel.trim() ? raw.classifierModel.trim() : null,
|
|
156
|
+
},
|
|
157
|
+
skipped,
|
|
158
|
+
};
|
|
146
159
|
} catch {
|
|
147
160
|
return { rules: EMPTY_RULES, skipped: [] };
|
|
148
161
|
}
|
|
@@ -345,6 +358,7 @@ async function callClassifierOnce(
|
|
|
345
358
|
model: NonNullable<ExtensionContext["model"]>,
|
|
346
359
|
userMessage: string,
|
|
347
360
|
maxTokens: number,
|
|
361
|
+
thinking: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" = "off",
|
|
348
362
|
): Promise<{ ok: true; text: string; stopReason: string } | { ok: false; error: string }> {
|
|
349
363
|
const signals = [AbortSignal.timeout(CLASSIFIER_TIMEOUT_MS)];
|
|
350
364
|
if (ctx.signal) signals.push(ctx.signal);
|
|
@@ -359,14 +373,14 @@ async function callClassifierOnce(
|
|
|
359
373
|
signal: AbortSignal.any(signals),
|
|
360
374
|
maxTokens,
|
|
361
375
|
temperature: 0,
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
376
|
+
// 思考参数必须用 API 原生字段(thinkingEnabled/effort),而非 reasoning
|
|
377
|
+
// (API 层 complete() 无此字段,宽类型索引签名静默放行后运行时丢弃——见
|
|
378
|
+
// research/thinking-param-blackhole.md)。
|
|
379
|
+
// 缺省 off = 显式关思考(实证送达 thinking:{"type":"disabled"},GLM 降为
|
|
380
|
+
// effort low 轻思考);后缀级别经 adaptive effort 送达(minimal→low 映射)。
|
|
381
|
+
...(thinking === "off"
|
|
382
|
+
? { thinkingEnabled: false }
|
|
383
|
+
: { thinkingEnabled: true, effort: thinking === "minimal" ? ("low" as const) : thinking }),
|
|
370
384
|
cacheRetention: "short",
|
|
371
385
|
sessionId: ctx.sessionManager.getSessionId(),
|
|
372
386
|
},
|
|
@@ -391,6 +405,7 @@ async function classifyWithModel(
|
|
|
391
405
|
ctx: ExtensionContext,
|
|
392
406
|
model: NonNullable<ExtensionContext["model"]>,
|
|
393
407
|
actionLine: string,
|
|
408
|
+
thinking: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" = "off",
|
|
394
409
|
): Promise<ClassifierOutcome> {
|
|
395
410
|
const transcript = buildTranscript(ctx, actionLine);
|
|
396
411
|
const userMessage = `<transcript>\n${transcript}\n</transcript>\nJudge the LAST action in the transcript above. Your entire response MUST begin with <verdict>.`;
|
|
@@ -398,7 +413,7 @@ async function classifyWithModel(
|
|
|
398
413
|
const failures: string[] = [];
|
|
399
414
|
for (const [n, maxTokens] of attempts) {
|
|
400
415
|
if (ctx.signal?.aborted) break; // 用户已取消,不再重试
|
|
401
|
-
const r = await callClassifierOnce(ctx, model, userMessage, maxTokens);
|
|
416
|
+
const r = await callClassifierOnce(ctx, model, userMessage, maxTokens, thinking);
|
|
402
417
|
if (r.ok) {
|
|
403
418
|
const diag = `stopReason=${r.stopReason}, model=${model.id}, 原始输出=${JSON.stringify(r.text.slice(0, 200))}`;
|
|
404
419
|
if (r.stopReason !== "error" && r.stopReason !== "aborted") {
|
|
@@ -540,7 +555,7 @@ function shadowTag(probe: ShadowProbe): string {
|
|
|
540
555
|
|
|
541
556
|
export default function autoMode(pi: ExtensionAPI) {
|
|
542
557
|
pi.registerFlag("auto-mode", { description: "Enable Auto Mode (rules + model classifier gating for tool calls)", type: "boolean", default: true });
|
|
543
|
-
pi.registerFlag("auto-mode-model", { description: "Classifier model as provider/id (default: inherit session model)", type: "string" });
|
|
558
|
+
pi.registerFlag("auto-mode-model", { description: "Classifier model as provider/id[:thinking] (pi --model syntax; default: inherit session model)", type: "string" });
|
|
544
559
|
pi.registerFlag("auto-mode-debug", { description: "Notify every verdict incl. allows, with shadow-cache annotation", type: "boolean", default: false });
|
|
545
560
|
|
|
546
561
|
let enabled = pi.getFlag("auto-mode") !== false;
|
|
@@ -590,14 +605,44 @@ export default function autoMode(pi: ExtensionAPI) {
|
|
|
590
605
|
},
|
|
591
606
|
});
|
|
592
607
|
|
|
608
|
+
let warnedClassifierModel = false;
|
|
609
|
+
/** 思考级别集(pi 原生 EXTENDED_THINKING_LEVELS;后缀语法对齐 pi --model provider/id:thinking) */
|
|
610
|
+
const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
611
|
+
|
|
612
|
+
/** 解析 "provider/id:thinking" → { specPart, level }。无效后缀 → 忽略并警告一次 */
|
|
613
|
+
function parseModelSpec(raw: string, ctx: ExtensionContext): { specPart: string; level: string | null } {
|
|
614
|
+
const slash = raw.lastIndexOf("/");
|
|
615
|
+
const colon = raw.lastIndexOf(":");
|
|
616
|
+
if (colon > slash + 1 && THINKING_LEVELS.has(raw.slice(colon + 1))) {
|
|
617
|
+
return { specPart: raw.slice(0, colon), level: raw.slice(colon + 1) };
|
|
618
|
+
}
|
|
619
|
+
if (colon > slash + 1 && !warnedClassifierModel) {
|
|
620
|
+
warnedClassifierModel = true;
|
|
621
|
+
ctx.ui.notify(`pi-verdict:思考级别后缀 "${raw.slice(colon + 1)}" 无效(合法:${[...THINKING_LEVELS].join("/")}),已忽略`, "warning");
|
|
622
|
+
}
|
|
623
|
+
return { specPart: raw, level: null };
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** 分类器思考级别:spec 后缀指定;缺省 off(显式关思考,blackhole 研究背书) */
|
|
627
|
+
let classifierThinking: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" = "off";
|
|
628
|
+
|
|
593
629
|
function resolveClassifierModel(ctx: ExtensionContext): NonNullable<ExtensionContext["model"]> | null {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
630
|
+
// 优先级:CLI flag > 环境变量 > 配置文件(classifierModel) > 自省(会话模型)
|
|
631
|
+
const raw =
|
|
632
|
+
(pi.getFlag("auto-mode-model") as string | undefined) ?? process.env.PI_AUTO_MODE_MODEL ?? userRules.classifierModel;
|
|
633
|
+
classifierThinking = "off";
|
|
634
|
+
if (raw) {
|
|
635
|
+
const { specPart, level } = parseModelSpec(raw, ctx);
|
|
636
|
+
if (level) classifierThinking = level as typeof classifierThinking;
|
|
637
|
+
const slash = specPart.indexOf("/");
|
|
597
638
|
if (slash > 0) {
|
|
598
|
-
const model = ctx.modelRegistry.find(
|
|
639
|
+
const model = ctx.modelRegistry.find(specPart.slice(0, slash), specPart.slice(slash + 1));
|
|
599
640
|
if (model && ctx.modelRegistry.hasConfiguredAuth(model)) return model;
|
|
600
641
|
}
|
|
642
|
+
if (!warnedClassifierModel) {
|
|
643
|
+
warnedClassifierModel = true; // 每会话仅警告一次,避免逐调用刷屏
|
|
644
|
+
ctx.ui.notify(`pi-verdict:分类器模型 "${raw}" 不可用(未找到或未配置凭证),回退会话模型(自省)`, "warning");
|
|
645
|
+
}
|
|
601
646
|
}
|
|
602
647
|
return ctx.model ?? null; // 自省:继承当前会话模型
|
|
603
648
|
}
|
|
@@ -635,7 +680,7 @@ export default function autoMode(pi: ExtensionAPI) {
|
|
|
635
680
|
const ctxKey = shadowContextKey(ctx);
|
|
636
681
|
const probe = shadow.probe(cmdKey, ctxKey);
|
|
637
682
|
|
|
638
|
-
const outcome = await classifyWithModel(ctx, model, action);
|
|
683
|
+
const outcome = await classifyWithModel(ctx, model, action, classifierThinking);
|
|
639
684
|
|
|
640
685
|
// 影子回记:真实模型 allow/deny 入缓存;ask 与 fail-closed 不入(#5 定案);
|
|
641
686
|
// 命中且本次为可缓存裁决时,对比反事实一致性
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-verdict",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Pi coding agent extension: automatic tool-call permission verdicts — rule layer + model classifier with three-state adjudication (allow/ask/deny), fail-closed, evidence-driven",
|
|
5
5
|
"author": "Jesset (https://github.com/jesset)",
|
|
6
6
|
"type": "module",
|