@gotgenes/pi-permission-system 5.1.1 → 5.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
@@ -5,6 +5,15 @@ 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
+ ## [5.1.2](https://github.com/gotgenes/pi-permission-system/compare/v5.1.1...v5.1.2) (2026-05-05)
9
+
10
+
11
+ ### Documentation
12
+
13
+ * fix README per-agent frontmatter example to flat format ([#78](https://github.com/gotgenes/pi-permission-system/issues/78)) ([1295427](https://github.com/gotgenes/pi-permission-system/commit/129542795218a6ada1f8d069a22b5ace5ec6c445))
14
+ * plan fix README frontmatter example and add missing tests ([#78](https://github.com/gotgenes/pi-permission-system/issues/78)) ([3fc99e1](https://github.com/gotgenes/pi-permission-system/commit/3fc99e1b3adf8193c94ac778617ca830488fa621))
15
+ * **retro:** add retro notes for issue [#93](https://github.com/gotgenes/pi-permission-system/issues/93) ([c9e8e89](https://github.com/gotgenes/pi-permission-system/commit/c9e8e89eb4057866198402add374d72a90a2fa2e))
16
+
8
17
  ## [5.1.1](https://github.com/gotgenes/pi-permission-system/compare/v5.1.0...v5.1.1) (2026-05-05)
9
18
 
10
19
 
package/README.md CHANGED
@@ -170,22 +170,21 @@ Override global permissions for specific agents via YAML frontmatter in the glob
170
170
  ---
171
171
  name: my-agent
172
172
  permission:
173
- tools:
174
- read: allow
175
- write: deny
176
- mcp: allow
173
+ read: allow
174
+ write: deny
175
+ mcp: allow
177
176
  bash:
178
177
  git status: allow
179
178
  git *: ask
180
179
  mcp:
181
180
  chrome_devtools_*: deny
182
181
  exa_*: allow
183
- skills:
182
+ skill:
184
183
  "*": ask
185
184
  ---
186
185
  ```
187
186
 
188
- **MCP behavior:** `permission.tools.mcp` is the coarse entry/fallback permission for a registered `mcp` tool when one is available. More specific `permission.mcp` target rules override that fallback when they match.
187
+ **MCP behavior:** `permission.mcp` is the coarse entry/fallback permission for a registered `mcp` tool when one is available. More specific `permission.mcp` target rules override that fallback when they match.
189
188
 
190
189
  **Limitations:** The frontmatter parser is intentionally minimal. Use only `key: value` scalars and nested maps. Avoid arrays, multi-line scalars, and YAML anchors.
191
190
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "5.1.1",
3
+ "version": "5.1.2",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "files": [
@@ -7,7 +7,7 @@ import {
7
7
  rmSync,
8
8
  writeFileSync,
9
9
  } from "node:fs";
10
- import { tmpdir } from "node:os";
10
+ import { homedir, tmpdir } from "node:os";
11
11
  import { dirname, join, resolve } from "node:path";
12
12
  import { test } from "vitest";
13
13
 
@@ -1831,6 +1831,188 @@ test("external_directory permission is not affected by unrelated surface keys",
1831
1831
  }
1832
1832
  });
1833
1833
 
1834
+ test("skill pattern map in agent frontmatter overrides global skill policy", () => {
1835
+ const { manager, cleanup } = createManager(
1836
+ {
1837
+ permission: { "*": "deny", skill: "deny" },
1838
+ },
1839
+ {
1840
+ reviewer: `---
1841
+ name: reviewer
1842
+ permission:
1843
+ skill:
1844
+ "*": ask
1845
+ "pi-*": allow
1846
+ ---
1847
+ `,
1848
+ },
1849
+ );
1850
+
1851
+ try {
1852
+ // Matches agent frontmatter pi-* pattern
1853
+ const allowed = manager.checkPermission(
1854
+ "skill",
1855
+ { name: "pi-code-review" },
1856
+ "reviewer",
1857
+ );
1858
+ assert.equal(allowed.state, "allow");
1859
+ assert.equal(allowed.matchedPattern, "pi-*");
1860
+ assert.equal(allowed.source, "skill");
1861
+
1862
+ // Falls through to agent frontmatter catch-all
1863
+ const asked = manager.checkPermission(
1864
+ "skill",
1865
+ { name: "other-skill" },
1866
+ "reviewer",
1867
+ );
1868
+ assert.equal(asked.state, "ask");
1869
+ assert.equal(asked.matchedPattern, "*");
1870
+
1871
+ // No agent override — global deny applies
1872
+ const denied = manager.checkPermission("skill", { name: "pi-code-review" });
1873
+ assert.equal(denied.state, "deny");
1874
+ assert.equal(denied.source, "skill");
1875
+ } finally {
1876
+ cleanup();
1877
+ }
1878
+ });
1879
+
1880
+ test("external_directory pattern map in agent frontmatter overrides global policy", () => {
1881
+ const { manager, cleanup } = createManager(
1882
+ {
1883
+ permission: { "*": "allow", external_directory: "deny" },
1884
+ },
1885
+ {
1886
+ trusted: `---
1887
+ name: trusted
1888
+ permission:
1889
+ external_directory:
1890
+ "*": deny
1891
+ "~/Downloads/*": allow
1892
+ ---
1893
+ `,
1894
+ },
1895
+ );
1896
+
1897
+ try {
1898
+ // Matches agent frontmatter ~/Downloads/* pattern
1899
+ const allowed = manager.checkPermission(
1900
+ "external_directory",
1901
+ { path: `${homedir()}/Downloads/file.txt` },
1902
+ "trusted",
1903
+ );
1904
+ assert.equal(allowed.state, "allow");
1905
+ assert.equal(allowed.matchedPattern, "~/Downloads/*");
1906
+ assert.equal(allowed.source, "special");
1907
+
1908
+ // Falls through to agent frontmatter catch-all deny
1909
+ const denied = manager.checkPermission(
1910
+ "external_directory",
1911
+ { path: `${homedir()}/Documents/secret.txt` },
1912
+ "trusted",
1913
+ );
1914
+ assert.equal(denied.state, "deny");
1915
+ assert.equal(denied.matchedPattern, "*");
1916
+
1917
+ // No agent override — global deny applies
1918
+ const globalDenied = manager.checkPermission("external_directory", {});
1919
+ assert.equal(globalDenied.state, "deny");
1920
+ assert.equal(globalDenied.source, "special");
1921
+ } finally {
1922
+ cleanup();
1923
+ }
1924
+ });
1925
+
1926
+ test("project-agent frontmatter skill rules override global-agent frontmatter skill rules", () => {
1927
+ const { manager, cleanup } = createManagerWithProject(
1928
+ {
1929
+ permission: { "*": "deny" },
1930
+ },
1931
+ {
1932
+ analyst: `---
1933
+ name: analyst
1934
+ permission:
1935
+ skill:
1936
+ "*": ask
1937
+ ---
1938
+ `,
1939
+ },
1940
+ {
1941
+ projectAgentFiles: {
1942
+ analyst: `---
1943
+ name: analyst
1944
+ permission:
1945
+ skill:
1946
+ "pi-*": allow
1947
+ "*": deny
1948
+ ---
1949
+ `,
1950
+ },
1951
+ },
1952
+ );
1953
+
1954
+ try {
1955
+ // Project-agent pi-* wins over global-agent *: ask
1956
+ const allowed = manager.checkPermission(
1957
+ "skill",
1958
+ { name: "pi-code-review" },
1959
+ "analyst",
1960
+ );
1961
+ assert.equal(allowed.state, "allow");
1962
+ assert.equal(allowed.matchedPattern, "pi-*");
1963
+
1964
+ // Project-agent *: deny wins over global-agent *: ask
1965
+ const denied = manager.checkPermission(
1966
+ "skill",
1967
+ { name: "other-skill" },
1968
+ "analyst",
1969
+ );
1970
+ assert.equal(denied.state, "deny");
1971
+ assert.equal(denied.matchedPattern, "*");
1972
+ } finally {
1973
+ cleanup();
1974
+ }
1975
+ });
1976
+
1977
+ test("project-agent frontmatter external_directory rules override global-agent frontmatter rules", () => {
1978
+ const { manager, cleanup } = createManagerWithProject(
1979
+ {
1980
+ permission: { "*": "allow", external_directory: "deny" },
1981
+ },
1982
+ {
1983
+ analyst: `---
1984
+ name: analyst
1985
+ permission:
1986
+ external_directory: ask
1987
+ ---
1988
+ `,
1989
+ },
1990
+ {
1991
+ projectAgentFiles: {
1992
+ analyst: `---
1993
+ name: analyst
1994
+ permission:
1995
+ external_directory: allow
1996
+ ---
1997
+ `,
1998
+ },
1999
+ },
2000
+ );
2001
+
2002
+ try {
2003
+ // Project-agent allow wins over global-agent ask
2004
+ const result = manager.checkPermission("external_directory", {}, "analyst");
2005
+ assert.equal(result.state, "allow");
2006
+ assert.equal(result.source, "special");
2007
+
2008
+ // Without agent context, global config deny applies
2009
+ const globalResult = manager.checkPermission("external_directory", {});
2010
+ assert.equal(globalResult.state, "deny");
2011
+ } finally {
2012
+ cleanup();
2013
+ }
2014
+ });
2015
+
1834
2016
  test("tool_call blocks path-bearing tools outside cwd when external_directory is denied", async () => {
1835
2017
  const rootDir = mkdtempSync(join(tmpdir(), "pi-permission-system-boundary-"));
1836
2018
  const cwd = join(rootDir, "repo");