@gotgenes/pi-permission-model-judge 1.1.1 → 1.1.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.2](https://github.com/gotgenes/pi-packages/compare/pi-permission-model-judge-v1.1.1...pi-permission-model-judge-v1.1.2) (2026-07-22)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **pi-permission-model-judge:** review typo paths embedded in bash commands ([#630](https://github.com/gotgenes/pi-packages/issues/630)) ([782c83d](https://github.com/gotgenes/pi-packages/commit/782c83d36d5d279c4932be315b9e78aae43ccb26))
9
+
10
+
11
+ ### Documentation
12
+
13
+ * **pi-permission-model-judge:** note bash-command paths are reviewed ([58a82e8](https://github.com/gotgenes/pi-packages/commit/58a82e8ca7a0df5552b767099004afc83e53be55))
14
+
3
15
  ## [1.1.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-model-judge-v1.1.0...pi-permission-model-judge-v1.1.1) (2026-07-22)
4
16
 
5
17
 
package/README.md CHANGED
@@ -20,6 +20,8 @@ The reviewer runs a short, cheap decision on each ask and defers at the first mi
20
20
  3. The path matches one of your `typoPatterns` (otherwise defer — no model call).
21
21
  4. The model confirms the typo and returns a teaching reason (`deny`), or is unsure (`defer`).
22
22
 
23
+ The candidate path comes from a file tool's path argument (`read`/`edit`/`write`) or from an external path referenced inside a `bash` command — a typo path in `cat …/pi-permission-system/packages/pi-permission-system/README.md` is reviewed the same way as one passed to `read`.
24
+
23
25
  It is fail-safe by construction: a missing model, invalid config, model timeout, unparseable reply, or an unsure verdict all resolve to `defer`.
24
26
  Deferring means the ask falls through to the normal permission prompt — this extension only ever *removes* a hand-denial, never grants access (it emits no `allow`).
25
27
 
@@ -55,6 +55,7 @@ The reviewer forces the model to call a single `report_verdict` tool, so the ver
55
55
  ### `typoPatterns`
56
56
 
57
57
  Each string is compiled with `new RegExp(pattern)` (no flags) and tested against the candidate path.
58
+ The candidate path is a file tool's path argument (`read`/`edit`/`write`) or an external path referenced inside a `bash` command — both surfaces are matched the same way.
58
59
  Only a path that matches at least one pattern is sent to the model — this is the cost gate that keeps the reviewer from calling the model on every ask.
59
60
  An invalid regular expression is skipped and recorded in the debug log (`model_judge.invalid_patterns`).
60
61
  An empty list (the default) matches nothing, so the reviewer defers everything.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-model-judge",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Deny-first typo-path model judge — a pi-permission-system Authorizer chain link",
5
5
  "type": "module",
6
6
  "imports": {
@@ -91,20 +91,29 @@ export function createTypoReviewer(
91
91
  return { kind: "defer" };
92
92
  }
93
93
  const { requestId } = details;
94
- const path = pathOf(details);
95
- if (path === undefined) {
94
+ const candidates = candidatePathsOf(details);
95
+ if (candidates.length === 0) {
96
96
  log.debug(SHORT_CIRCUIT_EVENT, { requestId, reason: "no-path" });
97
97
  return { kind: "defer" };
98
98
  }
99
- const matchedPattern = matchTypoPattern(path, compiledFor(config, log));
100
- if (matchedPattern === undefined) {
99
+ const compiled = compiledFor(config, log);
100
+ let matched: { path: string; matchedPattern: string } | undefined;
101
+ for (const candidate of candidates) {
102
+ const pattern = matchTypoPattern(candidate, compiled);
103
+ if (pattern !== undefined) {
104
+ matched = { path: candidate, matchedPattern: pattern };
105
+ break;
106
+ }
107
+ }
108
+ if (matched === undefined) {
101
109
  log.debug(SHORT_CIRCUIT_EVENT, {
102
110
  requestId,
103
- path,
111
+ path: candidates[0],
104
112
  reason: "pattern-miss",
105
113
  });
106
114
  return { kind: "defer" };
107
115
  }
116
+ const { path, matchedPattern } = matched;
108
117
 
109
118
  const modelId = `${config.provider}/${config.model}`;
110
119
  const base: DecisionBase = { requestId, path, matchedPattern, modelId };
@@ -177,9 +186,29 @@ function surfaceOf(details: PromptPermissionDetails): string | undefined {
177
186
  return details.accessIntent?.surface ?? details.surface ?? undefined;
178
187
  }
179
188
 
180
- /** The candidate path — `path` for a local ask, `value` for a forwarded one. */
181
- function pathOf(details: PromptPermissionDetails): string | undefined {
182
- return details.path ?? details.value ?? undefined;
189
+ /**
190
+ * Candidate paths to test against the typo patterns, most authoritative first.
191
+ *
192
+ * `accessIntent.matchValues` is the raising gate's authoritative path alias set
193
+ * (absolute ∪ cwd-relative ∪ canonical) — present for both file-tool and
194
+ * `bash` external_directory asks, and the only place a `bash`-surfaced path
195
+ * appears (the gate sets `command`/`accessIntent`, never `path`). `path` and
196
+ * `value` are fallbacks for a details bag without `accessIntent` (an older
197
+ * forwarded request or a hand-built local ask). `value` is last because a
198
+ * forwarded `bash` ask's display value is the command string, not a path.
199
+ */
200
+ function candidatePathsOf(details: PromptPermissionDetails): string[] {
201
+ const seen = new Set<string>();
202
+ for (const value of details.accessIntent?.matchValues ?? []) {
203
+ seen.add(value);
204
+ }
205
+ if (details.path !== undefined) {
206
+ seen.add(details.path);
207
+ }
208
+ if (details.value != null) {
209
+ seen.add(details.value);
210
+ }
211
+ return [...seen];
183
212
  }
184
213
 
185
214
  /**