@gotgenes/pi-permission-system 5.11.1 → 5.14.1
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 +79 -0
- package/package.json +3 -3
- package/src/extension-config.ts +2 -139
- package/src/logging.ts +4 -12
- package/src/pattern-suggest.ts +16 -3
- package/src/wildcard-matcher.ts +11 -3
- package/tests/config-modal.test.ts +20 -10
- package/tests/config-reporter.test.ts +2 -0
- package/tests/extension-config.test.ts +1 -60
- package/tests/handlers/external-directory-integration.test.ts +609 -0
- package/tests/handlers/external-directory-session-dedup.test.ts +367 -0
- package/tests/pattern-suggest.test.ts +30 -7
- package/tests/permission-system.test.ts +1 -122
- package/tests/wildcard-matcher.test.ts +91 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,85 @@ 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.14.1](https://github.com/gotgenes/pi-permission-system/compare/v5.14.0...v5.14.1) (2026-05-11)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* show tool name instead of bare wildcard in session-approval label ([1a65c30](https://github.com/gotgenes/pi-permission-system/commit/1a65c3017f25012c3a7ced63f26d40fcecea81d3))
|
|
14
|
+
* surface-prefixed session-approval labels for all permission surfaces ([759da03](https://github.com/gotgenes/pi-permission-system/commit/759da03be9c0d847ec6de58e161ef2e7cbbc70b8))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Documentation
|
|
18
|
+
|
|
19
|
+
* **retro:** add retro notes for issue [#122](https://github.com/gotgenes/pi-permission-system/issues/122) ([7867db2](https://github.com/gotgenes/pi-permission-system/commit/7867db22054df00e13aa6c88347238dadaa63166))
|
|
20
|
+
|
|
21
|
+
## [5.14.0](https://github.com/gotgenes/pi-permission-system/compare/v5.13.0...v5.14.0) (2026-05-09)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
* support ? single-character wildcard in permission patterns ([#122](https://github.com/gotgenes/pi-permission-system/issues/122)) ([7b56f49](https://github.com/gotgenes/pi-permission-system/commit/7b56f4979a3479912dcc1d903f52a517587b95f6))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Documentation
|
|
30
|
+
|
|
31
|
+
* document ? wildcard and update OpenCode compatibility ([#122](https://github.com/gotgenes/pi-permission-system/issues/122)) ([31ace5f](https://github.com/gotgenes/pi-permission-system/commit/31ace5f36a11299750994c2b9e6084dbecc240ba))
|
|
32
|
+
* plan ? single-character wildcard support ([#122](https://github.com/gotgenes/pi-permission-system/issues/122)) ([a7a2963](https://github.com/gotgenes/pi-permission-system/commit/a7a296337809ee7c107f86b87b64bfeb3709467f))
|
|
33
|
+
* **retro:** add retro notes for issue [#1](https://github.com/gotgenes/pi-permission-system/issues/1) ([b1c66f1](https://github.com/gotgenes/pi-permission-system/commit/b1c66f18d1aed5cc95a1fccb1a9c6c0f44f5cd11))
|
|
34
|
+
|
|
35
|
+
## [5.13.0](https://github.com/gotgenes/pi-permission-system/compare/v5.12.0...v5.13.0) (2026-05-08)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
### Features
|
|
39
|
+
|
|
40
|
+
* warn that this is a pnpm project on global npm pass-throughs ([f643149](https://github.com/gotgenes/pi-permission-system/commit/f64314981126331ec96ec6a20418440eff1738e7))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### Bug Fixes
|
|
44
|
+
|
|
45
|
+
* pass through npm install/uninstall -g in PATH shim ([eaf4256](https://github.com/gotgenes/pi-permission-system/commit/eaf4256446b2c1ec3ecba98d11cc75b1406931af))
|
|
46
|
+
* prevent double-loading extension in dev via project settings ([6c39f33](https://github.com/gotgenes/pi-permission-system/commit/6c39f33890e61b5e0fde41d178aad9df93592cc9))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
### Documentation
|
|
50
|
+
|
|
51
|
+
* plan external_directory integration tests ([#1](https://github.com/gotgenes/pi-permission-system/issues/1)) ([695ffeb](https://github.com/gotgenes/pi-permission-system/commit/695ffeb6d7a648df3dd9e18a01c20ff22266857b))
|
|
52
|
+
* **retro:** add retro notes for double-prompt investigation ([37734a5](https://github.com/gotgenes/pi-permission-system/commit/37734a57aee972aa4c732d92eeda424dd40443ee))
|
|
53
|
+
* **retro:** add retro notes for issue [#123](https://github.com/gotgenes/pi-permission-system/issues/123) ([5dbea33](https://github.com/gotgenes/pi-permission-system/commit/5dbea3379963b6231a4a101c889a4594705b12b6))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
### Miscellaneous Chores
|
|
57
|
+
|
|
58
|
+
* switch ask tool from pi-ask-user to @eko24ive/pi-ask ([0087458](https://github.com/gotgenes/pi-permission-system/commit/00874585724d79fbeda1fca84b998db3bcd1a043))
|
|
59
|
+
|
|
60
|
+
## [5.12.0](https://github.com/gotgenes/pi-permission-system/compare/v5.11.2...v5.12.0) (2026-05-08)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
### Features
|
|
64
|
+
|
|
65
|
+
* support trailing wildcard optionality ([#123](https://github.com/gotgenes/pi-permission-system/issues/123)) ([c25b0b5](https://github.com/gotgenes/pi-permission-system/commit/c25b0b5c59e5d5739a3b6c99de18444a4e7820ec))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
### Documentation
|
|
69
|
+
|
|
70
|
+
* plan trailing wildcard optionality ([#123](https://github.com/gotgenes/pi-permission-system/issues/123)) ([a6a50d2](https://github.com/gotgenes/pi-permission-system/commit/a6a50d28284bb624a3695d361b5b9f2a9861f470))
|
|
71
|
+
* **retro:** add retro notes for issue [#113](https://github.com/gotgenes/pi-permission-system/issues/113) ([7412740](https://github.com/gotgenes/pi-permission-system/commit/7412740c876c42cc1cd63f95ee4cb0ea0de72e68))
|
|
72
|
+
* update wildcard optionality docs ([#123](https://github.com/gotgenes/pi-permission-system/issues/123)) ([562adaf](https://github.com/gotgenes/pi-permission-system/commit/562adaff9726096004dc4f5214550af2dc2fc118))
|
|
73
|
+
|
|
74
|
+
## [5.11.2](https://github.com/gotgenes/pi-permission-system/compare/v5.11.1...v5.11.2) (2026-05-08)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
### Documentation
|
|
78
|
+
|
|
79
|
+
* plan removal of legacy path defaults from logging and extension-config ([#113](https://github.com/gotgenes/pi-permission-system/issues/113)) ([27ec0d8](https://github.com/gotgenes/pi-permission-system/commit/27ec0d8668c8c006f94c544e55d10c0eb272ddab))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
### Miscellaneous Chores
|
|
83
|
+
|
|
84
|
+
* approve @google/genai build scripts in pnpm-workspace.yaml ([23b177f](https://github.com/gotgenes/pi-permission-system/commit/23b177f8c5e00d5bc9812fa826c34844cc665d7a))
|
|
85
|
+
* upgrade pnpm to 11.0.8 and update deps ([31eb848](https://github.com/gotgenes/pi-permission-system/commit/31eb848539ff411cb7b6f422cd0244d6c9765ac7))
|
|
86
|
+
|
|
8
87
|
## [5.11.1](https://github.com/gotgenes/pi-permission-system/compare/v5.11.0...v5.11.1) (2026-05-08)
|
|
9
88
|
|
|
10
89
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotgenes/pi-permission-system",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.14.1",
|
|
4
4
|
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"@earendil-works/pi-tui": "*"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@biomejs/biome": "^2.4.
|
|
55
|
+
"@biomejs/biome": "^2.4.14",
|
|
56
56
|
"@earendil-works/pi-coding-agent": "^0.74.0",
|
|
57
57
|
"@earendil-works/pi-tui": "^0.74.0",
|
|
58
|
-
"@types/node": "^25.6.
|
|
58
|
+
"@types/node": "^25.6.2",
|
|
59
59
|
"markdownlint-cli2": "^0.22.1",
|
|
60
60
|
"typescript": "6.0.3",
|
|
61
61
|
"vitest": "^4.1.5"
|
package/src/extension-config.ts
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
existsSync,
|
|
3
|
-
mkdirSync,
|
|
4
|
-
readFileSync,
|
|
5
|
-
renameSync,
|
|
6
|
-
unlinkSync,
|
|
7
|
-
writeFileSync,
|
|
8
|
-
} from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
9
2
|
import { dirname, join } from "node:path";
|
|
10
3
|
import { fileURLToPath } from "node:url";
|
|
11
4
|
|
|
@@ -21,17 +14,6 @@ export interface PermissionSystemExtensionConfig {
|
|
|
21
14
|
piInfrastructureReadPaths?: string[];
|
|
22
15
|
}
|
|
23
16
|
|
|
24
|
-
export interface PermissionSystemConfigLoadResult {
|
|
25
|
-
config: PermissionSystemExtensionConfig;
|
|
26
|
-
created: boolean;
|
|
27
|
-
warning?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface PermissionSystemConfigSaveResult {
|
|
31
|
-
success: boolean;
|
|
32
|
-
error?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
17
|
export const DEFAULT_EXTENSION_CONFIG: PermissionSystemExtensionConfig = {
|
|
36
18
|
debugLog: false,
|
|
37
19
|
permissionReviewLog: true,
|
|
@@ -43,25 +25,6 @@ export function resolveExtensionRoot(moduleUrl = import.meta.url): string {
|
|
|
43
25
|
}
|
|
44
26
|
|
|
45
27
|
export const EXTENSION_ROOT = resolveExtensionRoot();
|
|
46
|
-
export const CONFIG_PATH = join(EXTENSION_ROOT, "config.json");
|
|
47
|
-
export const LOGS_DIR = join(EXTENSION_ROOT, "logs");
|
|
48
|
-
export const DEBUG_LOG_PATH = join(LOGS_DIR, `${EXTENSION_ID}-debug.jsonl`);
|
|
49
|
-
export const PERMISSION_REVIEW_LOG_PATH = join(
|
|
50
|
-
LOGS_DIR,
|
|
51
|
-
`${EXTENSION_ID}-permission-review.jsonl`,
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
function cloneDefaultConfig(): PermissionSystemExtensionConfig {
|
|
55
|
-
return {
|
|
56
|
-
debugLog: DEFAULT_EXTENSION_CONFIG.debugLog,
|
|
57
|
-
permissionReviewLog: DEFAULT_EXTENSION_CONFIG.permissionReviewLog,
|
|
58
|
-
yoloMode: DEFAULT_EXTENSION_CONFIG.yoloMode,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function createDefaultConfigContent(): string {
|
|
63
|
-
return `${JSON.stringify(DEFAULT_EXTENSION_CONFIG, null, 2)}\n`;
|
|
64
|
-
}
|
|
65
28
|
|
|
66
29
|
const PERMISSION_POLICY_KEYS: ReadonlySet<string> = new Set([
|
|
67
30
|
"defaultPolicy",
|
|
@@ -100,108 +63,8 @@ export function normalizePermissionSystemConfig(
|
|
|
100
63
|
return result;
|
|
101
64
|
}
|
|
102
65
|
|
|
103
|
-
function ensureConfigDirectory(configPath: string): void {
|
|
104
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function ensurePermissionSystemConfig(configPath = CONFIG_PATH): {
|
|
108
|
-
created: boolean;
|
|
109
|
-
warning?: string;
|
|
110
|
-
} {
|
|
111
|
-
if (existsSync(configPath)) {
|
|
112
|
-
return { created: false };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
ensureConfigDirectory(configPath);
|
|
117
|
-
writeFileSync(configPath, createDefaultConfigContent(), "utf-8");
|
|
118
|
-
return { created: true };
|
|
119
|
-
} catch (error) {
|
|
120
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
121
|
-
return {
|
|
122
|
-
created: false,
|
|
123
|
-
warning: `Failed to initialize permission-system config at '${configPath}': ${message}`,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function loadPermissionSystemConfig(
|
|
129
|
-
configPath = CONFIG_PATH,
|
|
130
|
-
): PermissionSystemConfigLoadResult {
|
|
131
|
-
const ensureResult = ensurePermissionSystemConfig(configPath);
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
const raw = readFileSync(configPath, "utf-8");
|
|
135
|
-
const parsed = JSON.parse(raw) as unknown;
|
|
136
|
-
const config = normalizePermissionSystemConfig(parsed);
|
|
137
|
-
const misplacedKeys = detectMisplacedPermissionKeys(toRecord(parsed));
|
|
138
|
-
|
|
139
|
-
const warnings: string[] = [];
|
|
140
|
-
if (ensureResult.warning) {
|
|
141
|
-
warnings.push(ensureResult.warning);
|
|
142
|
-
}
|
|
143
|
-
if (misplacedKeys.length > 0) {
|
|
144
|
-
warnings.push(
|
|
145
|
-
`config.json contains legacy permission-rule keys that are ignored: ${misplacedKeys.join(", ")}.\n` +
|
|
146
|
-
'Use the flat permission format: { "permission": { "*": "ask", "read": "allow", ... } }.\n' +
|
|
147
|
-
"See config/config.example.json for the new format.",
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
config,
|
|
153
|
-
created: ensureResult.created,
|
|
154
|
-
warning: warnings.length > 0 ? warnings.join("\n") : undefined,
|
|
155
|
-
};
|
|
156
|
-
} catch (error) {
|
|
157
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
158
|
-
return {
|
|
159
|
-
config: cloneDefaultConfig(),
|
|
160
|
-
created: ensureResult.created,
|
|
161
|
-
warning:
|
|
162
|
-
ensureResult.warning ??
|
|
163
|
-
`Failed to read permission-system config at '${configPath}': ${message}`,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export function savePermissionSystemConfig(
|
|
169
|
-
config: PermissionSystemExtensionConfig,
|
|
170
|
-
configPath = CONFIG_PATH,
|
|
171
|
-
): PermissionSystemConfigSaveResult {
|
|
172
|
-
const normalized = normalizePermissionSystemConfig(config);
|
|
173
|
-
const tmpPath = `${configPath}.tmp`;
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
ensureConfigDirectory(configPath);
|
|
177
|
-
writeFileSync(tmpPath, `${JSON.stringify(normalized, null, 2)}\n`, "utf-8");
|
|
178
|
-
renameSync(tmpPath, configPath);
|
|
179
|
-
return { success: true };
|
|
180
|
-
} catch (error) {
|
|
181
|
-
try {
|
|
182
|
-
if (existsSync(tmpPath)) {
|
|
183
|
-
unlinkSync(tmpPath);
|
|
184
|
-
}
|
|
185
|
-
} catch {
|
|
186
|
-
// Ignore cleanup failures.
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
190
|
-
return {
|
|
191
|
-
success: false,
|
|
192
|
-
error: `Failed to save permission-system config at '${configPath}': ${message}`,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export function getPermissionSystemConfigPath(
|
|
198
|
-
configPath = CONFIG_PATH,
|
|
199
|
-
): string {
|
|
200
|
-
return configPath;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
66
|
export function ensurePermissionSystemLogsDirectory(
|
|
204
|
-
logsDir
|
|
67
|
+
logsDir: string,
|
|
205
68
|
): string | undefined {
|
|
206
69
|
try {
|
|
207
70
|
mkdirSync(logsDir, { recursive: true });
|
package/src/logging.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { appendFileSync } from "node:fs";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
DEBUG_LOG_PATH,
|
|
5
4
|
EXTENSION_ID,
|
|
6
|
-
ensurePermissionSystemLogsDirectory,
|
|
7
|
-
LOGS_DIR,
|
|
8
|
-
PERMISSION_REVIEW_LOG_PATH,
|
|
9
5
|
type PermissionSystemExtensionConfig,
|
|
10
6
|
} from "./extension-config";
|
|
11
7
|
|
|
@@ -48,19 +44,15 @@ export interface PermissionSystemLogger {
|
|
|
48
44
|
|
|
49
45
|
interface PermissionSystemLoggerOptions {
|
|
50
46
|
getConfig: () => PermissionSystemExtensionConfig;
|
|
51
|
-
debugLogPath
|
|
52
|
-
reviewLogPath
|
|
53
|
-
ensureLogsDirectory
|
|
47
|
+
debugLogPath: string;
|
|
48
|
+
reviewLogPath: string;
|
|
49
|
+
ensureLogsDirectory: () => string | undefined;
|
|
54
50
|
}
|
|
55
51
|
|
|
56
52
|
export function createPermissionSystemLogger(
|
|
57
53
|
options: PermissionSystemLoggerOptions,
|
|
58
54
|
): PermissionSystemLogger {
|
|
59
|
-
const debugLogPath = options
|
|
60
|
-
const reviewLogPath = options.reviewLogPath ?? PERMISSION_REVIEW_LOG_PATH;
|
|
61
|
-
const ensureLogsDirectory =
|
|
62
|
-
options.ensureLogsDirectory ??
|
|
63
|
-
(() => ensurePermissionSystemLogsDirectory(LOGS_DIR));
|
|
55
|
+
const { debugLogPath, reviewLogPath, ensureLogsDirectory } = options;
|
|
64
56
|
|
|
65
57
|
const writeLine = (
|
|
66
58
|
stream: "debug" | "review",
|
package/src/pattern-suggest.ts
CHANGED
|
@@ -57,8 +57,21 @@ export function suggestMcpPattern(target: string): string {
|
|
|
57
57
|
return "*";
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
/** Surface-aware human-readable labels for the session-approval option. */
|
|
61
|
+
function buildLabel(pattern: string, surface: string): string {
|
|
62
|
+
switch (surface) {
|
|
63
|
+
case "bash":
|
|
64
|
+
return `Yes, allow bash "${pattern}" for this session`;
|
|
65
|
+
case "mcp":
|
|
66
|
+
return `Yes, allow mcp tool "${pattern}" for this session`;
|
|
67
|
+
case "skill":
|
|
68
|
+
return `Yes, allow skill "${pattern}" for this session`;
|
|
69
|
+
case "external_directory":
|
|
70
|
+
return `Yes, allow access to external directory "${pattern}" for this session`;
|
|
71
|
+
default:
|
|
72
|
+
// Tool surfaces (read, write, edit, grep, find, ls, extension tools)
|
|
73
|
+
return `Yes, allow tool "${surface}" for this session`;
|
|
74
|
+
}
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
/**
|
|
@@ -92,5 +105,5 @@ export function suggestSessionPattern(
|
|
|
92
105
|
break;
|
|
93
106
|
}
|
|
94
107
|
|
|
95
|
-
return { surface, pattern, label: buildLabel(pattern) };
|
|
108
|
+
return { surface, pattern, label: buildLabel(pattern, surface) };
|
|
96
109
|
}
|
package/src/wildcard-matcher.ts
CHANGED
|
@@ -21,11 +21,18 @@ export function compileWildcardPattern<TState>(
|
|
|
21
21
|
state: TState,
|
|
22
22
|
): CompiledWildcardPattern<TState> {
|
|
23
23
|
const expanded = expandHomePath(pattern);
|
|
24
|
-
|
|
24
|
+
let escaped = expanded
|
|
25
25
|
.split("*")
|
|
26
|
-
.map((part) => escapeRegExp(part))
|
|
26
|
+
.map((part) => escapeRegExp(part).replaceAll("\\?", "."))
|
|
27
27
|
.join(".*");
|
|
28
28
|
|
|
29
|
+
// If the pattern ends with " *" (space + wildcard), make the trailing
|
|
30
|
+
// space-and-arguments portion optional so that e.g. "git *" matches both
|
|
31
|
+
// "git status" and bare "git". Mirrors OpenCode wildcard semantics.
|
|
32
|
+
if (escaped.endsWith(" .*")) {
|
|
33
|
+
escaped = escaped.slice(0, -3) + "( .*)?";
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
return {
|
|
30
37
|
pattern,
|
|
31
38
|
state,
|
|
@@ -62,7 +69,8 @@ export function findCompiledWildcardMatch<TState>(
|
|
|
62
69
|
|
|
63
70
|
/**
|
|
64
71
|
* Test whether `value` matches `pattern` using wildcard rules.
|
|
65
|
-
* `*`
|
|
72
|
+
* `*` matches any sequence of characters (including empty).
|
|
73
|
+
* `?` matches exactly one character.
|
|
66
74
|
* Used by evaluate() for rule matching.
|
|
67
75
|
*/
|
|
68
76
|
export function wildcardMatch(pattern: string, value: string): boolean {
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { test, vi } from "vitest";
|
|
6
6
|
import { registerPermissionSystemCommand } from "../src/config-modal";
|
|
7
7
|
import {
|
|
8
8
|
DEFAULT_EXTENSION_CONFIG,
|
|
9
|
-
|
|
9
|
+
normalizePermissionSystemConfig,
|
|
10
10
|
type PermissionSystemExtensionConfig,
|
|
11
|
-
savePermissionSystemConfig,
|
|
12
11
|
} from "../src/extension-config";
|
|
13
12
|
import type { Rule } from "../src/rule";
|
|
14
13
|
|
|
@@ -136,17 +135,28 @@ test("permission-system command handlers manage config summary, persistence, and
|
|
|
136
135
|
};
|
|
137
136
|
|
|
138
137
|
try {
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
writeFileSync(
|
|
139
|
+
configPath,
|
|
140
|
+
`${JSON.stringify(normalizePermissionSystemConfig(config), null, 2)}\n`,
|
|
141
|
+
"utf-8",
|
|
142
|
+
);
|
|
141
143
|
|
|
142
144
|
const controller = {
|
|
143
145
|
getConfig: () => config,
|
|
144
146
|
setConfig: (next: PermissionSystemExtensionConfig) => {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
const currentConfig = normalizePermissionSystemConfig(
|
|
148
|
+
JSON.parse(readFileSync(configPath, "utf-8")) as unknown,
|
|
149
|
+
);
|
|
150
|
+
const normalized = normalizePermissionSystemConfig(next);
|
|
151
|
+
writeFileSync(
|
|
152
|
+
configPath,
|
|
153
|
+
`${JSON.stringify(normalized, null, 2)}\n`,
|
|
154
|
+
"utf-8",
|
|
155
|
+
);
|
|
156
|
+
config = normalizePermissionSystemConfig(
|
|
157
|
+
JSON.parse(readFileSync(configPath, "utf-8")) as unknown,
|
|
158
|
+
);
|
|
159
|
+
assert.notDeepEqual(config, currentConfig);
|
|
150
160
|
},
|
|
151
161
|
getConfigPath: () => configPath,
|
|
152
162
|
};
|
|
@@ -100,6 +100,7 @@ test("config.resolved entry appears in review log via logger", () => {
|
|
|
100
100
|
const logsDir = join(tempDir, "logs");
|
|
101
101
|
mkdirSync(logsDir, { recursive: true });
|
|
102
102
|
const reviewLogPath = join(logsDir, "review.jsonl");
|
|
103
|
+
const debugLogPath = join(logsDir, "debug.jsonl");
|
|
103
104
|
|
|
104
105
|
const globalConfigPath = join(tempDir, "pi-permissions.jsonc");
|
|
105
106
|
writeFileSync(globalConfigPath, "{}", "utf-8");
|
|
@@ -116,6 +117,7 @@ test("config.resolved entry appears in review log via logger", () => {
|
|
|
116
117
|
permissionReviewLog: true,
|
|
117
118
|
yoloMode: false,
|
|
118
119
|
}),
|
|
120
|
+
debugLogPath,
|
|
119
121
|
reviewLogPath,
|
|
120
122
|
ensureLogsDirectory: () => undefined,
|
|
121
123
|
});
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
5
2
|
|
|
6
3
|
import {
|
|
7
4
|
detectMisplacedPermissionKeys,
|
|
8
|
-
loadPermissionSystemConfig,
|
|
9
5
|
normalizePermissionSystemConfig,
|
|
10
6
|
} from "../src/extension-config";
|
|
11
7
|
|
|
@@ -78,61 +74,6 @@ describe("detectMisplacedPermissionKeys", () => {
|
|
|
78
74
|
});
|
|
79
75
|
});
|
|
80
76
|
|
|
81
|
-
describe("loadPermissionSystemConfig", () => {
|
|
82
|
-
let tempDir: string;
|
|
83
|
-
let configPath: string;
|
|
84
|
-
|
|
85
|
-
beforeEach(() => {
|
|
86
|
-
tempDir = mkdtempSync(join(tmpdir(), "perm-config-test-"));
|
|
87
|
-
configPath = join(tempDir, "config.json");
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
afterEach(() => {
|
|
91
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("returns no warning for a clean config", () => {
|
|
95
|
-
writeFileSync(
|
|
96
|
-
configPath,
|
|
97
|
-
JSON.stringify({
|
|
98
|
-
debugLog: false,
|
|
99
|
-
permissionReviewLog: true,
|
|
100
|
-
yoloMode: false,
|
|
101
|
-
}),
|
|
102
|
-
);
|
|
103
|
-
const result = loadPermissionSystemConfig(configPath);
|
|
104
|
-
expect(result.warning).toBeUndefined();
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("returns a warning naming misplaced permission-rule keys", () => {
|
|
108
|
-
writeFileSync(
|
|
109
|
-
configPath,
|
|
110
|
-
JSON.stringify({
|
|
111
|
-
debugLog: true,
|
|
112
|
-
defaultPolicy: { tools: "ask" },
|
|
113
|
-
bash: { "git status": "allow" },
|
|
114
|
-
}),
|
|
115
|
-
);
|
|
116
|
-
const result = loadPermissionSystemConfig(configPath);
|
|
117
|
-
expect(result.warning).toBeDefined();
|
|
118
|
-
expect(result.warning).toContain("defaultPolicy");
|
|
119
|
-
expect(result.warning).toContain("bash");
|
|
120
|
-
expect(result.warning).toContain("permission");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("still returns the valid extension config fields when misplaced keys are present", () => {
|
|
124
|
-
writeFileSync(
|
|
125
|
-
configPath,
|
|
126
|
-
JSON.stringify({
|
|
127
|
-
debugLog: true,
|
|
128
|
-
bash: { "git status": "allow" },
|
|
129
|
-
}),
|
|
130
|
-
);
|
|
131
|
-
const result = loadPermissionSystemConfig(configPath);
|
|
132
|
-
expect(result.config.debugLog).toBe(true);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
77
|
describe("normalizePermissionSystemConfig", () => {
|
|
137
78
|
it("normalizes a valid config object", () => {
|
|
138
79
|
const result = normalizePermissionSystemConfig({
|