@gotgenes/pi-permission-system 3.8.0 → 3.9.0

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
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.9.0](https://github.com/gotgenes/pi-permission-system/compare/v3.8.0...v3.9.0) (2026-05-03)
9
+
10
+
11
+ ### Features
12
+
13
+ * add normalizeConfig and defaults modules ([84f9c3e](https://github.com/gotgenes/pi-permission-system/commit/84f9c3ef1c665e8d55b694ecf8bbec2dff41b093))
14
+ * evaluate() accepts optional defaultAction parameter ([69dde81](https://github.com/gotgenes/pi-permission-system/commit/69dde81d05722065307b04102a8af6935df0e17c))
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * remove unused imports flagged by biome ([62704a3](https://github.com/gotgenes/pi-permission-system/commit/62704a3b5c833af74a3bd27942ffc4247c92c12c))
20
+
21
+
22
+ ### Documentation
23
+
24
+ * mark [#42](https://github.com/gotgenes/pi-permission-system/issues/42) and [#43](https://github.com/gotgenes/pi-permission-system/issues/43) complete in target architecture ([04430f2](https://github.com/gotgenes/pi-permission-system/commit/04430f2ea000e9607541262a71ad2be633dc7bb6))
25
+ * mark [#56](https://github.com/gotgenes/pi-permission-system/issues/56) complete in target architecture ([2fe95c5](https://github.com/gotgenes/pi-permission-system/commit/2fe95c577ec97e78c1390db91020294ba25662e0))
26
+ * plan unify Rule type and normalize config into flat Ruleset ([#56](https://github.com/gotgenes/pi-permission-system/issues/56)) ([61e8c48](https://github.com/gotgenes/pi-permission-system/commit/61e8c4800f39a173b69f2773e8b2f09fa9c7318b))
27
+ * **retro:** add retro notes for issue [#43](https://github.com/gotgenes/pi-permission-system/issues/43) ([bd6aea6](https://github.com/gotgenes/pi-permission-system/commit/bd6aea6ed2e1dfdad1ef610f9abb8319d87460cd))
28
+
8
29
  ## [3.8.0](https://github.com/gotgenes/pi-permission-system/compare/v3.7.0...v3.8.0) (2026-05-03)
9
30
 
10
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,60 @@
1
+ import type { PermissionDefaultPolicy, PermissionState } from "./types";
2
+
3
+ /** Hardcoded fallback — every surface defaults to "ask" (least privilege). */
4
+ export const DEFAULT_POLICY: PermissionDefaultPolicy = {
5
+ tools: "ask",
6
+ bash: "ask",
7
+ mcp: "ask",
8
+ skills: "ask",
9
+ special: "ask",
10
+ };
11
+
12
+ /**
13
+ * Map a surface name used in evaluate() to the corresponding
14
+ * defaultPolicy key. Surfaces not listed here fall through to
15
+ * either "tools" or "special" via getSurfaceDefault().
16
+ */
17
+ const SURFACE_TO_DEFAULT_KEY: Record<string, keyof PermissionDefaultPolicy> = {
18
+ bash: "bash",
19
+ mcp: "mcp",
20
+ skill: "skills",
21
+ };
22
+
23
+ /**
24
+ * Resolve the default action for a surface, consulting merged defaults.
25
+ *
26
+ * - "bash", "mcp", "skill" → dedicated defaultPolicy key
27
+ * - special-key surfaces (e.g. "external_directory") → defaults.special
28
+ * - everything else (tool-name surfaces) → defaults.tools
29
+ */
30
+ export function getSurfaceDefault(
31
+ surface: string,
32
+ defaults: PermissionDefaultPolicy,
33
+ specialKeys: ReadonlySet<string>,
34
+ ): PermissionState {
35
+ const key = SURFACE_TO_DEFAULT_KEY[surface];
36
+ if (key) return defaults[key];
37
+ if (specialKeys.has(surface)) return defaults.special;
38
+ return defaults.tools;
39
+ }
40
+
41
+ /**
42
+ * Merge zero or more partial default policies on top of DEFAULT_POLICY.
43
+ * Later partials override earlier ones (shallow spread per key).
44
+ */
45
+ export function mergeDefaults(
46
+ ...partials: ReadonlyArray<Partial<PermissionDefaultPolicy> | undefined>
47
+ ): PermissionDefaultPolicy {
48
+ const merged: PermissionDefaultPolicy = { ...DEFAULT_POLICY };
49
+
50
+ for (const partial of partials) {
51
+ if (!partial) continue;
52
+ if (partial.tools) merged.tools = partial.tools;
53
+ if (partial.bash) merged.bash = partial.bash;
54
+ if (partial.mcp) merged.mcp = partial.mcp;
55
+ if (partial.skills) merged.skills = partial.skills;
56
+ if (partial.special) merged.special = partial.special;
57
+ }
58
+
59
+ return merged;
60
+ }
package/src/index.ts CHANGED
@@ -11,10 +11,7 @@ import {
11
11
  handleSessionStart,
12
12
  handleToolCall,
13
13
  } from "./handlers";
14
- import {
15
- type PermissionPromptDecision,
16
- requestPermissionDecisionFromUi,
17
- } from "./permission-dialog";
14
+ import { requestPermissionDecisionFromUi } from "./permission-dialog";
18
15
  import {
19
16
  createExtensionRuntime,
20
17
  createPermissionManagerForCwd,
@@ -0,0 +1,70 @@
1
+ import type { Rule, Ruleset } from "./rule";
2
+ import type { PermissionState } from "./types";
3
+
4
+ /**
5
+ * Subset of UnifiedPermissionConfig covering only policy fields.
6
+ * Used as the input shape for normalizeConfig().
7
+ */
8
+ export interface NormalizableConfig {
9
+ tools?: Record<string, PermissionState>;
10
+ bash?: Record<string, PermissionState>;
11
+ mcp?: Record<string, PermissionState>;
12
+ skills?: Record<string, PermissionState>;
13
+ special?: Record<string, PermissionState>;
14
+ }
15
+
16
+ /**
17
+ * Keys in the `tools` map that serve as fallback defaults for their
18
+ * respective pattern-based surfaces rather than as tool-level rules.
19
+ *
20
+ * `tools.bash` sets the bash default (fallback when no bash pattern matches).
21
+ * `tools.mcp` sets the tool-level MCP fallback.
22
+ *
23
+ * These are NOT normalized into the Ruleset — they are extracted by the
24
+ * caller and handled as separate fallbacks to preserve the semantic that
25
+ * specific bash/mcp patterns always have priority.
26
+ */
27
+ export const TOOL_SURFACE_OVERRIDE_KEYS: ReadonlySet<string> = new Set([
28
+ "bash",
29
+ "mcp",
30
+ ]);
31
+
32
+ /**
33
+ * Convert the on-disk config shape into a flat Ruleset.
34
+ *
35
+ * Ordering within a scope:
36
+ * 1. tools entries (tool-name-as-surface, pattern "*") — excluding bash/mcp
37
+ * 2. bash entries (surface "bash", pattern = command glob)
38
+ * 3. mcp entries (surface "mcp", pattern = target glob)
39
+ * 4. skills entries (surface "skill", pattern = skill glob)
40
+ * 5. special entries (surface "special", pattern = key name)
41
+ *
42
+ * `tools.bash` and `tools.mcp` are excluded — see TOOL_SURFACE_OVERRIDE_KEYS.
43
+ * `defaultPolicy` is NOT included — handled separately by the caller.
44
+ */
45
+ export function normalizeConfig(config: NormalizableConfig): Ruleset {
46
+ const rules: Rule[] = [];
47
+
48
+ for (const [name, action] of Object.entries(config.tools ?? {})) {
49
+ if (TOOL_SURFACE_OVERRIDE_KEYS.has(name)) continue;
50
+ rules.push({ surface: name, pattern: "*", action });
51
+ }
52
+
53
+ for (const [pattern, action] of Object.entries(config.bash ?? {})) {
54
+ rules.push({ surface: "bash", pattern, action });
55
+ }
56
+
57
+ for (const [pattern, action] of Object.entries(config.mcp ?? {})) {
58
+ rules.push({ surface: "mcp", pattern, action });
59
+ }
60
+
61
+ for (const [pattern, action] of Object.entries(config.skills ?? {})) {
62
+ rules.push({ surface: "skill", pattern, action });
63
+ }
64
+
65
+ for (const [name, action] of Object.entries(config.special ?? {})) {
66
+ rules.push({ surface: "special", pattern: name, action });
67
+ }
68
+
69
+ return rules;
70
+ }