@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 +21 -0
- package/package.json +1 -1
- package/src/system-prompt-sanitizer.ts +22 -1
- package/tests/bash-filter.test.ts +7 -2
- package/tests/external-directory.test.ts +7 -3
- package/tests/permission-prompts.test.ts +5 -2
- package/tests/system-prompt-sanitizer.test.ts +42 -1
- package/tests/tool-input-preview.test.ts +5 -2
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
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|