@herbertgao/pi-extensions 2026.9.7 → 2026.9.9
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 +8 -4
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +4 -4
- package/node_modules/@herbertgao/resume-from/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +6 -1
- package/node_modules/@narumitw/pi-btw/dist/index.ts +209 -23
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
- package/node_modules/@narumitw/pi-btw/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +1 -12
- package/node_modules/@narumitw/pi-btw/src/menu.ts +244 -15
- package/node_modules/@narumitw/pi-btw/src/settings.ts +5 -0
- package/node_modules/grok-mermaid/CHANGELOG.md +46 -0
- package/node_modules/grok-mermaid/LICENSE +205 -0
- package/node_modules/grok-mermaid/README.md +191 -0
- package/node_modules/grok-mermaid/dist/ansi.d.ts +16 -0
- package/node_modules/grok-mermaid/dist/ansi.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/ansi.js +23 -0
- package/node_modules/grok-mermaid/dist/ansi.js.map +1 -0
- package/node_modules/grok-mermaid/dist/canvas.d.ts +87 -0
- package/node_modules/grok-mermaid/dist/canvas.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/canvas.js +366 -0
- package/node_modules/grok-mermaid/dist/canvas.js.map +1 -0
- package/node_modules/grok-mermaid/dist/graph.d.ts +74 -0
- package/node_modules/grok-mermaid/dist/graph.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/graph.js +91 -0
- package/node_modules/grok-mermaid/dist/graph.js.map +1 -0
- package/node_modules/grok-mermaid/dist/index.d.ts +32 -0
- package/node_modules/grok-mermaid/dist/index.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/index.js +100 -0
- package/node_modules/grok-mermaid/dist/index.js.map +1 -0
- package/node_modules/grok-mermaid/dist/labels.d.ts +62 -0
- package/node_modules/grok-mermaid/dist/labels.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/labels.js +324 -0
- package/node_modules/grok-mermaid/dist/labels.js.map +1 -0
- package/node_modules/grok-mermaid/dist/layout-seq.d.ts +12 -0
- package/node_modules/grok-mermaid/dist/layout-seq.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/layout-seq.js +194 -0
- package/node_modules/grok-mermaid/dist/layout-seq.js.map +1 -0
- package/node_modules/grok-mermaid/dist/layout.d.ts +87 -0
- package/node_modules/grok-mermaid/dist/layout.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/layout.js +881 -0
- package/node_modules/grok-mermaid/dist/layout.js.map +1 -0
- package/node_modules/grok-mermaid/dist/parse.d.ts +83 -0
- package/node_modules/grok-mermaid/dist/parse.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/parse.js +1151 -0
- package/node_modules/grok-mermaid/dist/parse.js.map +1 -0
- package/node_modules/grok-mermaid/dist/source-box.d.ts +18 -0
- package/node_modules/grok-mermaid/dist/source-box.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/source-box.js +78 -0
- package/node_modules/grok-mermaid/dist/source-box.js.map +1 -0
- package/node_modules/grok-mermaid/dist/types.d.ts +42 -0
- package/node_modules/grok-mermaid/dist/types.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/types.js +1 -0
- package/node_modules/grok-mermaid/dist/types.js.map +1 -0
- package/node_modules/grok-mermaid/dist/width-data.d.ts +2 -0
- package/node_modules/grok-mermaid/dist/width-data.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/width-data.js +994 -0
- package/node_modules/grok-mermaid/dist/width-data.js.map +1 -0
- package/node_modules/grok-mermaid/dist/width.d.ts +18 -0
- package/node_modules/grok-mermaid/dist/width.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/width.js +76 -0
- package/node_modules/grok-mermaid/dist/width.js.map +1 -0
- package/node_modules/grok-mermaid/package.json +49 -0
- package/node_modules/grok-mermaid/src/ansi.ts +34 -0
- package/node_modules/grok-mermaid/src/canvas.ts +373 -0
- package/node_modules/grok-mermaid/src/graph.ts +142 -0
- package/node_modules/grok-mermaid/src/index.ts +104 -0
- package/node_modules/grok-mermaid/src/labels.ts +326 -0
- package/node_modules/grok-mermaid/src/layout-seq.ts +203 -0
- package/node_modules/grok-mermaid/src/layout.ts +1015 -0
- package/node_modules/grok-mermaid/src/parse.ts +1189 -0
- package/node_modules/grok-mermaid/src/source-box.ts +89 -0
- package/node_modules/grok-mermaid/src/types.ts +43 -0
- package/node_modules/grok-mermaid/src/width-data.ts +993 -0
- package/node_modules/grok-mermaid/src/width.ts +74 -0
- package/node_modules/pi-jev-auto-mode/CHANGELOG.md +110 -0
- package/node_modules/pi-jev-auto-mode/LICENSE +21 -0
- package/node_modules/pi-jev-auto-mode/README.md +285 -0
- package/node_modules/pi-jev-auto-mode/SECURITY.md +32 -0
- package/node_modules/pi-jev-auto-mode/docs/calibration.md +154 -0
- package/node_modules/pi-jev-auto-mode/docs/design.md +164 -0
- package/node_modules/pi-jev-auto-mode/docs/security.md +124 -0
- package/node_modules/pi-jev-auto-mode/index.ts +1 -0
- package/node_modules/pi-jev-auto-mode/package.json +72 -0
- package/node_modules/pi-jev-auto-mode/src/call.ts +180 -0
- package/node_modules/pi-jev-auto-mode/src/decide.ts +86 -0
- package/node_modules/pi-jev-auto-mode/src/extension.ts +862 -0
- package/node_modules/pi-jev-auto-mode/src/intent.ts +71 -0
- package/node_modules/pi-jev-auto-mode/src/jev/availability.ts +53 -0
- package/node_modules/pi-jev-auto-mode/src/jev/criteria.ts +19 -0
- package/node_modules/pi-jev-auto-mode/src/jev/decide.ts +187 -0
- package/node_modules/pi-jev-auto-mode/src/jev/engine.ts +165 -0
- package/node_modules/pi-jev-auto-mode/src/jev/index.ts +20 -0
- package/node_modules/pi-jev-auto-mode/src/jev/questions.ts +273 -0
- package/node_modules/pi-jev-auto-mode/src/jev/response.ts +64 -0
- package/node_modules/pi-jev-auto-mode/src/jev/state.ts +20 -0
- package/node_modules/pi-jev-auto-mode/src/jev/transport.ts +117 -0
- package/node_modules/pi-jev-auto-mode/src/jev/types.ts +46 -0
- package/node_modules/pi-jev-auto-mode/src/policy.ts +558 -0
- package/node_modules/pi-jev-auto-mode/src/records.ts +118 -0
- package/node_modules/pi-jev-auto-mode/src/settings.ts +327 -0
- package/node_modules/pi-jev-auto-mode/src/ui.ts +233 -0
- package/node_modules/pi-lens/CHANGELOG.md +45 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +21 -9
- package/node_modules/pi-lens/dist/clients/cascade-format.js +24 -0
- package/node_modules/pi-lens/dist/clients/extension-log.js +48 -1
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +11 -12
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +3 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +41 -7
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +9 -2
- package/node_modules/pi-lens/dist/clients/path-utils.js +51 -9
- package/node_modules/pi-lens/dist/clients/persistent-reverify.js +271 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +15 -1
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +124 -7
- package/node_modules/pi-lens/dist/clients/skills-resolver.js +38 -2
- package/node_modules/pi-lens/dist/clients/widget-state.js +152 -6
- package/node_modules/pi-lens/dist/index.js +729 -344
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +14 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +84 -17
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +32 -5
- package/node_modules/pi-lens/package.json +1 -1
- package/node_modules/pi-typesafe/LICENSE +21 -0
- package/node_modules/pi-typesafe/README.md +153 -0
- package/node_modules/pi-typesafe/dist/ask.d.ts +32 -0
- package/node_modules/pi-typesafe/dist/ask.js +25 -0
- package/node_modules/pi-typesafe/dist/auth.d.ts +56 -0
- package/node_modules/pi-typesafe/dist/auth.js +114 -0
- package/node_modules/pi-typesafe/dist/batch.d.ts +74 -0
- package/node_modules/pi-typesafe/dist/batch.js +116 -0
- package/node_modules/pi-typesafe/dist/calibrate.d.ts +106 -0
- package/node_modules/pi-typesafe/dist/calibrate.js +157 -0
- package/node_modules/pi-typesafe/dist/client.d.ts +69 -0
- package/node_modules/pi-typesafe/dist/client.js +188 -0
- package/node_modules/pi-typesafe/dist/credentials.d.ts +46 -0
- package/node_modules/pi-typesafe/dist/credentials.js +112 -0
- package/node_modules/pi-typesafe/dist/errors.d.ts +9 -0
- package/node_modules/pi-typesafe/dist/errors.js +31 -0
- package/node_modules/pi-typesafe/dist/extension.d.ts +3 -0
- package/node_modules/pi-typesafe/dist/extension.js +222 -0
- package/node_modules/pi-typesafe/dist/index.d.ts +18 -0
- package/node_modules/pi-typesafe/dist/index.js +9 -0
- package/node_modules/pi-typesafe/dist/key-prompt.d.ts +3 -0
- package/node_modules/pi-typesafe/dist/key-prompt.js +43 -0
- package/node_modules/pi-typesafe/dist/login.d.ts +27 -0
- package/node_modules/pi-typesafe/dist/login.js +38 -0
- package/node_modules/pi-typesafe/dist/schema.d.ts +43 -0
- package/node_modules/pi-typesafe/dist/schema.js +152 -0
- package/node_modules/pi-typesafe/dist/ui.d.ts +3 -0
- package/node_modules/pi-typesafe/dist/ui.js +3 -0
- package/node_modules/pi-typesafe/dist/usage.d.ts +73 -0
- package/node_modules/pi-typesafe/dist/usage.js +182 -0
- package/node_modules/pi-typesafe/examples/decision-extension.ts +41 -0
- package/node_modules/pi-typesafe/extensions/index.js +2 -0
- package/node_modules/pi-typesafe/package.json +89 -0
- package/package.json +16 -8
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn a Pi tool call into the shape Jev will judge.
|
|
3
|
+
*
|
|
4
|
+
* What leaves this module is what a third party (TypeSafe) gets to see, so the
|
|
5
|
+
* boundary is explicit: the command text and the target path are sent, file
|
|
6
|
+
* contents, diffs, and tool output are not. Obvious credentials are redacted on
|
|
7
|
+
* the way out.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { classifyWriteTarget } from "./policy.ts";
|
|
11
|
+
import type { JevJson, JevState } from "./jev/state.ts";
|
|
12
|
+
|
|
13
|
+
export type GatedTool = "bash" | "write" | "edit";
|
|
14
|
+
|
|
15
|
+
export const GATED_TOOLS: readonly GatedTool[] = ["bash", "write", "edit"];
|
|
16
|
+
|
|
17
|
+
export interface ToolCallEventLike {
|
|
18
|
+
readonly toolName: string;
|
|
19
|
+
readonly toolCallId?: string;
|
|
20
|
+
readonly input: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface GatedCall {
|
|
24
|
+
readonly tool: GatedTool;
|
|
25
|
+
/** One-line description used in records and dialogs. */
|
|
26
|
+
readonly summary: string;
|
|
27
|
+
/** bash: the command, truncated and redacted. Absent for file tools. */
|
|
28
|
+
readonly command?: string;
|
|
29
|
+
/** write/edit: absolute target path. */
|
|
30
|
+
readonly path?: string;
|
|
31
|
+
/** write/edit: target relative to the working directory, when inside it. */
|
|
32
|
+
readonly relativePath?: string;
|
|
33
|
+
readonly outsideCwd: boolean;
|
|
34
|
+
readonly protectedReason?: string;
|
|
35
|
+
/** write/edit: number of edit hunks. */
|
|
36
|
+
readonly editCount?: number;
|
|
37
|
+
/** write: content size in characters. The content itself is never sent. */
|
|
38
|
+
readonly contentLength?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CallOptions {
|
|
42
|
+
readonly cwd: string;
|
|
43
|
+
readonly maxCommandLength?: number;
|
|
44
|
+
/** Additional protected locations from settings. */
|
|
45
|
+
readonly extraProtectedPaths?: readonly string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const DEFAULT_MAX_COMMAND_LENGTH = 4000;
|
|
49
|
+
|
|
50
|
+
const SECRET_PATTERNS: readonly { readonly pattern: RegExp; readonly replacement: string }[] = [
|
|
51
|
+
{
|
|
52
|
+
pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
53
|
+
replacement: "<redacted-private-key>",
|
|
54
|
+
},
|
|
55
|
+
{ pattern: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}\b/g, replacement: "<redacted-jwt>" },
|
|
56
|
+
{ pattern: /\b(?:sk|rk)-[A-Za-z0-9_-]{16,}\b/g, replacement: "<redacted-key>" },
|
|
57
|
+
{ pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}\b/g, replacement: "<redacted-token>" },
|
|
58
|
+
{ pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{12,}\b/g, replacement: "<redacted-aws-key>" },
|
|
59
|
+
{ pattern: /\bapikey_[A-Za-z0-9_-]{8,}\b/gi, replacement: "<redacted-typesafe-key>" },
|
|
60
|
+
{ pattern: /\bBearer\s+[A-Za-z0-9._~+/-]{12,}=*/g, replacement: "Bearer <redacted>" },
|
|
61
|
+
{
|
|
62
|
+
pattern:
|
|
63
|
+
/((?:api[_-]?key|secret|token|password|passwd|access[_-]?key|client[_-]?secret|auth[_-]?token)\s*[:=]\s*)(["']?)([^\s"';|&]{6,})/gi,
|
|
64
|
+
replacement: "$1$2<redacted>",
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
/** Replace obvious credentials so they are neither sent nor displayed. */
|
|
69
|
+
export function redactSecrets(text: string): string {
|
|
70
|
+
let result = text;
|
|
71
|
+
for (const { pattern, replacement } of SECRET_PATTERNS) {
|
|
72
|
+
result = result.replace(pattern, replacement);
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function truncate(value: string, maxLength: number): string {
|
|
78
|
+
return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function asString(value: unknown): string | undefined {
|
|
82
|
+
return typeof value === "string" ? value : undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Build the gate's view of a tool call.
|
|
87
|
+
*
|
|
88
|
+
* Returns `undefined` for tools this extension does not gate.
|
|
89
|
+
*/
|
|
90
|
+
export function buildGatedCall(event: ToolCallEventLike, options: CallOptions): GatedCall | undefined {
|
|
91
|
+
const maxCommandLength = options.maxCommandLength ?? DEFAULT_MAX_COMMAND_LENGTH;
|
|
92
|
+
|
|
93
|
+
if (event.toolName === "bash") {
|
|
94
|
+
const rawCommand = asString(event.input.command) ?? "";
|
|
95
|
+
const command = truncate(redactSecrets(rawCommand), maxCommandLength);
|
|
96
|
+
const firstLine = rawCommand.split("\n")[0] ?? rawCommand;
|
|
97
|
+
return {
|
|
98
|
+
tool: "bash",
|
|
99
|
+
summary: truncate(redactSecrets(firstLine.trim()), 200) || "(empty command)",
|
|
100
|
+
command,
|
|
101
|
+
outsideCwd: false,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (event.toolName === "write" || event.toolName === "edit") {
|
|
106
|
+
const inputPath = asString(event.input.path) ?? "";
|
|
107
|
+
const target = classifyWriteTarget(inputPath, options.cwd, options.extraProtectedPaths ?? []);
|
|
108
|
+
const editCount = Array.isArray(event.input.edits) ? event.input.edits.length : undefined;
|
|
109
|
+
const contentLength = asString(event.input.content)?.length;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
tool: event.toolName,
|
|
113
|
+
summary: `${event.toolName} ${target.relativeToCwd ?? target.absolute}`,
|
|
114
|
+
path: target.absolute,
|
|
115
|
+
relativePath: target.relativeToCwd,
|
|
116
|
+
outsideCwd: target.outsideCwd,
|
|
117
|
+
protectedReason: target.protectedReason,
|
|
118
|
+
editCount,
|
|
119
|
+
contentLength,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface RepoFacts {
|
|
127
|
+
readonly cwd: string;
|
|
128
|
+
readonly isGitRepository: boolean;
|
|
129
|
+
readonly protectedPaths: readonly string[];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface JevStateInput {
|
|
133
|
+
readonly call: GatedCall;
|
|
134
|
+
readonly reasons: readonly string[];
|
|
135
|
+
/** Recent user-authored text. Never assistant or tool output. */
|
|
136
|
+
readonly intent: string;
|
|
137
|
+
/** User-authored policy notes. */
|
|
138
|
+
readonly policy: string;
|
|
139
|
+
readonly repo: RepoFacts;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const NO_POLICY_PLACEHOLDER = "(no user policy configured)";
|
|
143
|
+
export const NO_INTENT_PLACEHOLDER = "(no recent user message available)";
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Assemble the Jev request state.
|
|
147
|
+
*
|
|
148
|
+
* The user policy is `context` (session-scoped) so conditions can name it, and
|
|
149
|
+
* the call plus the user intent are `value` (per-call).
|
|
150
|
+
*/
|
|
151
|
+
export function toJevState(input: JevStateInput): JevState {
|
|
152
|
+
const { call } = input;
|
|
153
|
+
|
|
154
|
+
const value: Record<string, JevJson> = {
|
|
155
|
+
tool: call.tool,
|
|
156
|
+
operation: call.summary,
|
|
157
|
+
matched_policy_reasons: [...input.reasons],
|
|
158
|
+
user_intent: input.intent.trim() || NO_INTENT_PLACEHOLDER,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (call.command !== undefined) value.command = call.command;
|
|
162
|
+
if (call.path !== undefined) {
|
|
163
|
+
value.path = call.path;
|
|
164
|
+
value.relative_path = call.relativePath ?? null;
|
|
165
|
+
value.outside_working_directory = call.outsideCwd;
|
|
166
|
+
}
|
|
167
|
+
if (call.editCount !== undefined) value.edit_count = call.editCount;
|
|
168
|
+
if (call.contentLength !== undefined) value.content_length = call.contentLength;
|
|
169
|
+
|
|
170
|
+
const context: Record<string, JevJson> = {
|
|
171
|
+
policy: input.policy.trim() || NO_POLICY_PLACEHOLDER,
|
|
172
|
+
repository: {
|
|
173
|
+
cwd: input.repo.cwd,
|
|
174
|
+
is_git_repository: input.repo.isGitRepository,
|
|
175
|
+
protected_paths: [...input.repo.protectedPaths],
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
return { value, context };
|
|
180
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The decision-engine seam.
|
|
3
|
+
*
|
|
4
|
+
* The gate itself (ordering, blocking, recording) is deterministic. Everything
|
|
5
|
+
* probabilistic sits behind `DecisionEngine`, so the extension can be tested
|
|
6
|
+
* without a network and so the Jev implementation can be swapped or disabled
|
|
7
|
+
* without touching the safety-critical path.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { GatedCall, RepoFacts } from "./call.ts";
|
|
11
|
+
|
|
12
|
+
export type DecisionSource = "hard-deny" | "user-rule" | "engine" | "uncertain" | "unavailable" | "no-ui" | "user";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A display-ready report of one condition's judgment.
|
|
16
|
+
*
|
|
17
|
+
* `verdict` is the band the probability fell into; `ignored` means a hazard-mode
|
|
18
|
+
* condition landed in the middle band, which is not evidence of anything. This is
|
|
19
|
+
* what a record shows during threshold tuning.
|
|
20
|
+
*/
|
|
21
|
+
export interface ConditionReport {
|
|
22
|
+
readonly ruleId: string;
|
|
23
|
+
readonly label: string;
|
|
24
|
+
readonly probability: number;
|
|
25
|
+
readonly threshold: number;
|
|
26
|
+
readonly verdict: "satisfied" | "rejected" | "uncertain" | "ignored";
|
|
27
|
+
readonly clearedByIntent: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface EngineEvidence {
|
|
31
|
+
/** Per-condition probability, when the engine exposes one. */
|
|
32
|
+
readonly probabilities?: Readonly<Record<string, number>>;
|
|
33
|
+
/** The threshold each probability was compared against. */
|
|
34
|
+
readonly thresholds?: Readonly<Record<string, number>>;
|
|
35
|
+
/** One entry per condition that was asked. */
|
|
36
|
+
readonly conditions?: readonly ConditionReport[];
|
|
37
|
+
/** The condition that decided the call, when one did. */
|
|
38
|
+
readonly decidingRule?: string;
|
|
39
|
+
/** Conditions whose clear rejection the user's own request cleared. */
|
|
40
|
+
readonly clearedByIntent?: readonly string[];
|
|
41
|
+
readonly model?: string;
|
|
42
|
+
readonly latencyMs?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type EngineVerdict =
|
|
46
|
+
| ({ readonly verdict: "allow"; readonly rationale: string } & EngineEvidence)
|
|
47
|
+
| ({ readonly verdict: "deny"; readonly rationale: string } & EngineEvidence)
|
|
48
|
+
| ({ readonly verdict: "uncertain"; readonly rationale: string } & EngineEvidence)
|
|
49
|
+
| ({ readonly verdict: "unavailable"; readonly rationale: string; readonly reason: string } & EngineEvidence);
|
|
50
|
+
|
|
51
|
+
export interface CandidateInput {
|
|
52
|
+
readonly call: GatedCall;
|
|
53
|
+
/** Names of the policy patterns this call matched. */
|
|
54
|
+
readonly reasons: readonly string[];
|
|
55
|
+
/**
|
|
56
|
+
* Whether the deterministic layer recognised the call as a dangerous shape.
|
|
57
|
+
* Required rather than optional: a caller that forgets it would silently skip the
|
|
58
|
+
* intent question, which is the wrong direction to fail in.
|
|
59
|
+
*/
|
|
60
|
+
readonly flagged: boolean;
|
|
61
|
+
readonly intent: string;
|
|
62
|
+
readonly policy: string;
|
|
63
|
+
readonly repo: RepoFacts;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface JudgeOptions {
|
|
67
|
+
readonly signal?: AbortSignal;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface DecisionEngine {
|
|
71
|
+
readonly id: string;
|
|
72
|
+
judge(input: CandidateInput, options: JudgeOptions): Promise<EngineVerdict>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Milestone-1 engine: no semantic judgment at all.
|
|
77
|
+
*
|
|
78
|
+
* The gate checks for this engine before judging: with no key there is nothing to
|
|
79
|
+
* judge with, so it says so and blocks the call instead of inventing a verdict.
|
|
80
|
+
*/
|
|
81
|
+
export function createManualEngine(): DecisionEngine {
|
|
82
|
+
return {
|
|
83
|
+
id: "manual",
|
|
84
|
+
judge: () => Promise.resolve({ verdict: "uncertain", rationale: "No semantic decision engine is configured." }),
|
|
85
|
+
};
|
|
86
|
+
}
|