@gotgenes/pi-permission-system 3.0.1 → 3.0.3

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.0.3](https://github.com/gotgenes/pi-permission-system/compare/v3.0.2...v3.0.3) (2026-05-03)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * stop findSection at first non-body line instead of EOF ([#33](https://github.com/gotgenes/pi-permission-system/issues/33)) ([15c178e](https://github.com/gotgenes/pi-permission-system/commit/15c178ea1aa42c091885f0aeacd87ba5b298ce24))
14
+
15
+
16
+ ### Documentation
17
+
18
+ * plan fix for findSection greedy end boundary ([#33](https://github.com/gotgenes/pi-permission-system/issues/33)) ([72a5f9e](https://github.com/gotgenes/pi-permission-system/commit/72a5f9e4ab14cc9959781994cdc7dc52f1dfa657))
19
+ * **retro:** add retro notes for issue [#35](https://github.com/gotgenes/pi-permission-system/issues/35) ([89830cb](https://github.com/gotgenes/pi-permission-system/commit/89830cb7c562ed092d4f08031584f0e0855326de))
20
+
21
+ ## [3.0.2](https://github.com/gotgenes/pi-permission-system/compare/v3.0.1...v3.0.2) (2026-05-03)
22
+
23
+
24
+ ### Documentation
25
+
26
+ * plan align test mock-cleanup and node:* default-export rules ([#35](https://github.com/gotgenes/pi-permission-system/issues/35)) ([480aa02](https://github.com/gotgenes/pi-permission-system/commit/480aa02183009a6693a6699948563345871f198d))
27
+ * **retro:** add retro notes for issue [#21](https://github.com/gotgenes/pi-permission-system/issues/21) ([c7aae09](https://github.com/gotgenes/pi-permission-system/commit/c7aae099a48e58506195d843de797a1ae45b723a))
28
+
8
29
  ## [3.0.1](https://github.com/gotgenes/pi-permission-system/compare/v3.0.0...v3.0.1) (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.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -94,6 +94,14 @@ function isTopLevelSectionHeader(line: string): boolean {
94
94
  );
95
95
  }
96
96
 
97
+ function isSectionBodyLine(line: string): boolean {
98
+ const trimmed = line.trim();
99
+ if (trimmed.length === 0) return true; // blank line
100
+ if (trimmed.startsWith("- ")) return true; // bullet
101
+ if (line !== line.trimStart()) return true; // indented
102
+ return false;
103
+ }
104
+
97
105
  function findSection(
98
106
  lines: readonly string[],
99
107
  header: string,
@@ -103,12 +111,25 @@ function findSection(
103
111
  return null;
104
112
  }
105
113
 
106
- let end = lines.length;
114
+ // If a subsequent recognised section header exists, use it as the boundary.
115
+ // This preserves the original behaviour for the common case where sections
116
+ // are adjacent (e.g. "Available tools:" followed by "Guidelines:") and
117
+ // ensures any prose continuation between the two headers is also removed.
107
118
  for (let index = start + 1; index < lines.length; index += 1) {
108
119
  if (isTopLevelSectionHeader(lines[index])) {
120
+ return { start, end: index };
121
+ }
122
+ }
123
+
124
+ // No subsequent section header — stop at the first non-body line so that
125
+ // content after the section (e.g. custom user notes) is not silently deleted.
126
+ let end = start + 1;
127
+ for (let index = start + 1; index < lines.length; index += 1) {
128
+ if (!isSectionBodyLine(lines[index])) {
109
129
  end = index;
110
130
  break;
111
131
  }
132
+ end = index + 1;
112
133
  }
113
134
 
114
135
  return { start, end };
@@ -1,4 +1,4 @@
1
- import { afterEach, describe, expect, test, vi } from "vitest";
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
2
 
3
3
  import type { PermissionState } from "../src/types.js";
4
4
 
@@ -20,10 +20,15 @@ import {
20
20
  findCompiledWildcardMatch,
21
21
  } from "../src/wildcard-matcher.js";
22
22
 
23
+ const mockedCompilePatterns = vi.mocked(compileWildcardPatterns);
23
24
  const mockedFindMatch = vi.mocked(findCompiledWildcardMatch);
24
25
 
26
+ beforeEach(() => {
27
+ mockedCompilePatterns.mockClear();
28
+ mockedFindMatch.mockReset();
29
+ });
30
+
25
31
  afterEach(() => {
26
- vi.clearAllMocks();
27
32
  vi.restoreAllMocks();
28
33
  });
29
34
 
@@ -2,9 +2,13 @@ import { join } from "node:path";
2
2
  import { afterEach, describe, expect, test, vi } from "vitest";
3
3
 
4
4
  // Mock node:os so tilde-expansion is deterministic across platforms.
5
- vi.mock("node:os", () => ({
6
- homedir: vi.fn(() => "/mock/home"),
7
- }));
5
+ vi.mock("node:os", () => {
6
+ const homedir = vi.fn(() => "/mock/home");
7
+ return {
8
+ homedir,
9
+ default: { homedir },
10
+ };
11
+ });
8
12
 
9
13
  import {
10
14
  formatExternalDirectoryAskPrompt,
@@ -1,4 +1,4 @@
1
- import { afterEach, describe, expect, test, vi } from "vitest";
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
2
 
3
3
  // Mock tool-input-preview collaborator before importing the module under test.
4
4
  vi.mock("../src/tool-input-preview.js", () => ({
@@ -22,8 +22,11 @@ import type { PermissionCheckResult } from "../src/types.js";
22
22
 
23
23
  const mockedFormatToolInput = vi.mocked(formatToolInputForPrompt);
24
24
 
25
+ beforeEach(() => {
26
+ mockedFormatToolInput.mockReset();
27
+ });
28
+
25
29
  afterEach(() => {
26
- vi.clearAllMocks();
27
30
  vi.restoreAllMocks();
28
31
  });
29
32
 
@@ -32,7 +32,7 @@ describe("sanitizeAvailableToolsSection — Available tools section", () => {
32
32
 
33
33
  // Bug #33: findSection extends to lines.length when no subsequent recognised
34
34
  // header follows, so content after the last section is silently deleted.
35
- test.fails("preserves content that follows the Available tools section (bug #33)", () => {
35
+ test("preserves content that follows the Available tools section (bug #33)", () => {
36
36
  const input = prompt(
37
37
  availableToolsSection(["bash", "read"]),
38
38
  "Other content",
@@ -184,3 +184,44 @@ describe("sanitizeAvailableToolsSection — multi-section prompt", () => {
184
184
  expect(result.prompt).not.toMatch(/\n{3,}/);
185
185
  });
186
186
  });
187
+
188
+ describe("sanitizeAvailableToolsSection — findSection boundary edge cases", () => {
189
+ test("preserves content after Guidelines when Guidelines is the last recognised section", () => {
190
+ const input = prompt(
191
+ guidelinesSection(["use bash for file operations like ls, rg, find"]),
192
+ "Trailing custom instructions",
193
+ );
194
+ const result = sanitizeAvailableToolsSection(input, []);
195
+ expect(result.prompt).toContain("Trailing custom instructions");
196
+ });
197
+
198
+ test("preserves trailing prose when both sections are removed", () => {
199
+ const input = prompt(
200
+ availableToolsSection(["bash"]),
201
+ guidelinesSection(["use bash for file operations like ls, rg, find"]),
202
+ "Important user note",
203
+ );
204
+ const result = sanitizeAvailableToolsSection(input, []);
205
+ expect(result.removed).toBe(true);
206
+ expect(result.prompt).not.toContain("Available tools:");
207
+ expect(result.prompt).not.toContain("Guidelines:");
208
+ expect(result.prompt).toContain("Important user note");
209
+ });
210
+
211
+ test("section at EOF with no trailing content still works", () => {
212
+ const input = availableToolsSection(["bash", "read"]);
213
+ const result = sanitizeAvailableToolsSection(input, ["bash", "read"]);
214
+ expect(result.removed).toBe(true);
215
+ expect(result.prompt).toBe("");
216
+ });
217
+
218
+ test("section followed by blank lines then prose — prose survives", () => {
219
+ const input = ["Available tools:", "- bash", "", "", "Custom note"].join(
220
+ "\n",
221
+ );
222
+ const result = sanitizeAvailableToolsSection(input, ["bash"]);
223
+ expect(result.removed).toBe(true);
224
+ expect(result.prompt).toContain("Custom note");
225
+ expect(result.prompt).not.toContain("Available tools:");
226
+ });
227
+ });
@@ -1,4 +1,4 @@
1
- import { afterEach, describe, expect, test, vi } from "vitest";
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
2
 
3
3
  // Mock logging collaborator before importing the module under test.
4
4
  vi.mock("../src/logging.js", () => ({
@@ -29,8 +29,11 @@ import type { PermissionCheckResult } from "../src/types.js";
29
29
 
30
30
  const mockedStringify = vi.mocked(safeJsonStringify);
31
31
 
32
+ beforeEach(() => {
33
+ mockedStringify.mockReset();
34
+ });
35
+
32
36
  afterEach(() => {
33
- vi.clearAllMocks();
34
37
  vi.restoreAllMocks();
35
38
  });
36
39