@gotgenes/pi-permission-system 3.0.2 → 3.0.4

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/index.ts +1 -1
  3. package/package.json +3 -2
  4. package/src/bash-filter.ts +2 -2
  5. package/src/common.ts +1 -1
  6. package/src/config-loader.ts +3 -3
  7. package/src/config-modal.ts +1 -1
  8. package/src/config-reporter.ts +1 -1
  9. package/src/extension-config.ts +1 -1
  10. package/src/external-directory.ts +1 -1
  11. package/src/forwarded-permissions/io.ts +2 -2
  12. package/src/forwarded-permissions/polling.ts +6 -6
  13. package/src/index.ts +23 -23
  14. package/src/logging.ts +1 -1
  15. package/src/permission-forwarding.ts +1 -1
  16. package/src/permission-manager.ts +6 -6
  17. package/src/permission-prompts.ts +3 -3
  18. package/src/skill-prompt-sanitizer.ts +2 -2
  19. package/src/status.ts +2 -2
  20. package/src/subagent-context.ts +1 -1
  21. package/src/system-prompt-sanitizer.ts +22 -1
  22. package/src/tool-input-preview.ts +3 -3
  23. package/src/tool-registry.ts +1 -1
  24. package/src/yolo-mode.ts +2 -2
  25. package/tests/active-agent.test.ts +1 -1
  26. package/tests/bash-filter.test.ts +3 -3
  27. package/tests/common.test.ts +1 -1
  28. package/tests/config-loader.test.ts +1 -1
  29. package/tests/config-modal.test.ts +2 -2
  30. package/tests/config-paths.test.ts +1 -1
  31. package/tests/config-reporter.test.ts +4 -4
  32. package/tests/extension-config.test.ts +1 -1
  33. package/tests/external-directory.test.ts +1 -1
  34. package/tests/permission-prompts.test.ts +4 -4
  35. package/tests/permission-system.test.ts +14 -14
  36. package/tests/session-start.test.ts +4 -4
  37. package/tests/skill-prompt-sanitizer.test.ts +3 -3
  38. package/tests/subagent-context.test.ts +2 -2
  39. package/tests/system-prompt-sanitizer.test.ts +43 -2
  40. package/tests/tool-input-preview.test.ts +3 -3
  41. package/tests/tool-registry.test.ts +1 -1
  42. package/tests/wildcard-matcher.test.ts +1 -1
  43. package/tests/yolo-mode.test.ts +2 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,32 @@ 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.4](https://github.com/gotgenes/pi-permission-system/compare/v3.0.3...v3.0.4) (2026-05-03)
9
+
10
+
11
+ ### Documentation
12
+
13
+ * plan drop .js extensions from internal imports ([#32](https://github.com/gotgenes/pi-permission-system/issues/32)) ([1d73759](https://github.com/gotgenes/pi-permission-system/commit/1d73759bafb4de02f20817d977eca181a5df5a54))
14
+ * **retro:** add retro notes for issue [#33](https://github.com/gotgenes/pi-permission-system/issues/33) ([4e4ef43](https://github.com/gotgenes/pi-permission-system/commit/4e4ef4397efe9fd0c75ac00b1b411eef50603f33))
15
+
16
+
17
+ ### Miscellaneous Chores
18
+
19
+ * add lint:imports guard against .js extensions ([#32](https://github.com/gotgenes/pi-permission-system/issues/32)) ([fa0b924](https://github.com/gotgenes/pi-permission-system/commit/fa0b924fb5a8741de294eff8b2f2f94aded34f5d))
20
+
21
+ ## [3.0.3](https://github.com/gotgenes/pi-permission-system/compare/v3.0.2...v3.0.3) (2026-05-03)
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * 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))
27
+
28
+
29
+ ### Documentation
30
+
31
+ * 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))
32
+ * **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))
33
+
8
34
  ## [3.0.2](https://github.com/gotgenes/pi-permission-system/compare/v3.0.1...v3.0.2) (2026-05-03)
9
35
 
10
36
 
package/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- import permissionSystemExtension from "./src/index.js";
1
+ import permissionSystemExtension from "./src/index";
2
2
 
3
3
  export default permissionSystemExtension;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -24,7 +24,8 @@
24
24
  "lint:fix": "biome check --write .",
25
25
  "lint:md": "markdownlint-cli2 '*.md' 'docs/**/*.md' '.pi/prompts/**/*.md'",
26
26
  "lint:md:fix": "markdownlint-cli2 --fix '*.md' 'docs/**/*.md' '.pi/prompts/**/*.md'",
27
- "lint:all": "npm run lint && npm run lint:md",
27
+ "lint:imports": "! grep -rn --include='*.ts' 'from \"\\.[./][^\"]*\\.js\"' src/ tests/ index.ts",
28
+ "lint:all": "npm run lint && npm run lint:md && npm run lint:imports",
28
29
  "format": "biome format --write .",
29
30
  "test": "vitest run",
30
31
  "test:watch": "vitest",
@@ -1,9 +1,9 @@
1
- import type { BashPermissions, PermissionState } from "./types.js";
1
+ import type { BashPermissions, PermissionState } from "./types";
2
2
  import {
3
3
  type CompiledWildcardPattern,
4
4
  compileWildcardPatterns,
5
5
  findCompiledWildcardMatch,
6
- } from "./wildcard-matcher.js";
6
+ } from "./wildcard-matcher";
7
7
 
8
8
  type CompiledPattern = CompiledWildcardPattern<PermissionState>;
9
9
 
package/src/common.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { PermissionState } from "./types.js";
1
+ import type { PermissionState } from "./types";
2
2
 
3
3
  export function toRecord(value: unknown): Record<string, unknown> {
4
4
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -1,15 +1,15 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { normalize } from "node:path";
3
3
 
4
- import { isPermissionState, toRecord } from "./common.js";
4
+ import { isPermissionState, toRecord } from "./common";
5
5
  import {
6
6
  getGlobalConfigPath,
7
7
  getLegacyExtensionConfigPath,
8
8
  getLegacyGlobalPolicyPath,
9
9
  getLegacyProjectPolicyPath,
10
10
  getProjectConfigPath,
11
- } from "./config-paths.js";
12
- import type { PermissionDefaultPolicy, PermissionState } from "./types.js";
11
+ } from "./config-paths";
12
+ import type { PermissionDefaultPolicy, PermissionState } from "./types";
13
13
 
14
14
  /**
15
15
  * Unified config shape combining runtime knobs and policy in one object.
@@ -8,7 +8,7 @@ import { type SettingItem, SettingsList } from "@mariozechner/pi-tui";
8
8
  import {
9
9
  DEFAULT_EXTENSION_CONFIG,
10
10
  type PermissionSystemExtensionConfig,
11
- } from "./extension-config.js";
11
+ } from "./extension-config";
12
12
 
13
13
  interface PermissionSystemConfigController {
14
14
  getConfig(): PermissionSystemExtensionConfig;
@@ -1,4 +1,4 @@
1
- import type { ResolvedPolicyPaths } from "./permission-manager.js";
1
+ import type { ResolvedPolicyPaths } from "./permission-manager";
2
2
 
3
3
  export interface ResolvedConfigLogEntry {
4
4
  globalConfigPath: string;
@@ -9,7 +9,7 @@ import {
9
9
  import { dirname, join } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
 
12
- import { toRecord } from "./common.js";
12
+ import { toRecord } from "./common";
13
13
 
14
14
  export const EXTENSION_ID = "pi-permission-system";
15
15
 
@@ -1,7 +1,7 @@
1
1
  import { homedir } from "node:os";
2
2
  import { join, normalize, resolve, sep } from "node:path";
3
3
 
4
- import { getNonEmptyString, toRecord } from "./common.js";
4
+ import { getNonEmptyString, toRecord } from "./common";
5
5
 
6
6
  export const PATH_BEARING_TOOLS = new Set([
7
7
  "read",
@@ -9,13 +9,13 @@ import {
9
9
  writeFileSync,
10
10
  } from "node:fs";
11
11
 
12
- import { isPermissionDecisionState } from "../permission-dialog.js";
12
+ import { isPermissionDecisionState } from "../permission-dialog";
13
13
  import {
14
14
  createPermissionForwardingLocation,
15
15
  type ForwardedPermissionRequest,
16
16
  type ForwardedPermissionResponse,
17
17
  type PermissionForwardingLocation,
18
- } from "../permission-forwarding.js";
18
+ } from "../permission-forwarding";
19
19
 
20
20
  type LogFn = (event: string, details: Record<string, unknown>) => void;
21
21
 
@@ -5,9 +5,9 @@ import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
5
5
  import {
6
6
  getActiveAgentName,
7
7
  getActiveAgentNameFromSystemPrompt,
8
- } from "../active-agent.js";
9
- import { toRecord } from "../common.js";
10
- import type { PermissionPromptDecision } from "../permission-dialog.js";
8
+ } from "../active-agent";
9
+ import { toRecord } from "../common";
10
+ import type { PermissionPromptDecision } from "../permission-dialog";
11
11
  import {
12
12
  type ForwardedPermissionRequest,
13
13
  type ForwardedPermissionResponse,
@@ -15,8 +15,8 @@ import {
15
15
  PERMISSION_FORWARDING_POLL_INTERVAL_MS,
16
16
  PERMISSION_FORWARDING_TIMEOUT_MS,
17
17
  resolvePermissionForwardingTargetSessionId,
18
- } from "../permission-forwarding.js";
19
- import { isSubagentExecutionContext } from "../subagent-context.js";
18
+ } from "../permission-forwarding";
19
+ import { isSubagentExecutionContext } from "../subagent-context";
20
20
 
21
21
  import {
22
22
  cleanupPermissionForwardingLocationIfEmpty,
@@ -30,7 +30,7 @@ import {
30
30
  safeDeleteFile,
31
31
  sleep,
32
32
  writeJsonFileAtomic,
33
- } from "./io.js";
33
+ } from "./io";
34
34
 
35
35
  export interface PermissionForwardingDeps {
36
36
  forwardingDir: string;
package/src/index.ts CHANGED
@@ -16,15 +16,15 @@ import {
16
16
  import {
17
17
  getActiveAgentName,
18
18
  getActiveAgentNameFromSystemPrompt,
19
- } from "./active-agent.js";
19
+ } from "./active-agent";
20
20
  import {
21
21
  createActiveToolsCacheKey,
22
22
  createBeforeAgentStartPromptStateKey,
23
23
  shouldApplyCachedAgentStartState,
24
- } from "./before-agent-start-cache.js";
25
- import { toRecord } from "./common.js";
26
- import { loadAndMergeConfigs, loadUnifiedConfig } from "./config-loader.js";
27
- import { registerPermissionSystemCommand } from "./config-modal.js";
24
+ } from "./before-agent-start-cache";
25
+ import { toRecord } from "./common";
26
+ import { loadAndMergeConfigs, loadUnifiedConfig } from "./config-loader";
27
+ import { registerPermissionSystemCommand } from "./config-modal";
28
28
  import {
29
29
  DEBUG_LOG_FILENAME,
30
30
  getGlobalConfigPath,
@@ -34,15 +34,15 @@ import {
34
34
  getLegacyProjectPolicyPath,
35
35
  getProjectConfigPath,
36
36
  REVIEW_LOG_FILENAME,
37
- } from "./config-paths.js";
38
- import { buildResolvedConfigLogEntry } from "./config-reporter.js";
37
+ } from "./config-paths";
38
+ import { buildResolvedConfigLogEntry } from "./config-reporter";
39
39
  import {
40
40
  DEFAULT_EXTENSION_CONFIG,
41
41
  EXTENSION_ROOT,
42
42
  ensurePermissionSystemLogsDirectory,
43
43
  normalizePermissionSystemConfig,
44
44
  type PermissionSystemExtensionConfig,
45
- } from "./extension-config.js";
45
+ } from "./extension-config";
46
46
  import {
47
47
  formatExternalDirectoryAskPrompt,
48
48
  formatExternalDirectoryDenyReason,
@@ -51,20 +51,20 @@ import {
51
51
  isPathOutsideWorkingDirectory,
52
52
  normalizePathForComparison,
53
53
  PATH_BEARING_TOOLS,
54
- } from "./external-directory.js";
55
- import { setForwardedPermissionLogger } from "./forwarded-permissions/io.js";
54
+ } from "./external-directory";
55
+ import { setForwardedPermissionLogger } from "./forwarded-permissions/io";
56
56
  import {
57
57
  confirmPermission,
58
58
  type PermissionForwardingDeps,
59
59
  processForwardedPermissionRequests,
60
- } from "./forwarded-permissions/polling.js";
61
- import { createPermissionSystemLogger } from "./logging.js";
60
+ } from "./forwarded-permissions/polling";
61
+ import { createPermissionSystemLogger } from "./logging";
62
62
  import {
63
63
  type PermissionPromptDecision,
64
64
  requestPermissionDecisionFromUi,
65
- } from "./permission-dialog.js";
66
- import { PERMISSION_FORWARDING_POLL_INTERVAL_MS } from "./permission-forwarding.js";
67
- import { PermissionManager } from "./permission-manager.js";
65
+ } from "./permission-dialog";
66
+ import { PERMISSION_FORWARDING_POLL_INTERVAL_MS } from "./permission-forwarding";
67
+ import { PermissionManager } from "./permission-manager";
68
68
  import {
69
69
  formatAskPrompt,
70
70
  formatDenyReason,
@@ -74,27 +74,27 @@ import {
74
74
  formatSkillPathDenyReason,
75
75
  formatUnknownToolReason,
76
76
  formatUserDeniedReason,
77
- } from "./permission-prompts.js";
77
+ } from "./permission-prompts";
78
78
  import {
79
79
  findSkillPathMatch,
80
80
  resolveSkillPromptEntries,
81
81
  type SkillPromptEntry,
82
- } from "./skill-prompt-sanitizer.js";
82
+ } from "./skill-prompt-sanitizer";
83
83
  import {
84
84
  PERMISSION_SYSTEM_STATUS_KEY,
85
85
  syncPermissionSystemStatus,
86
- } from "./status.js";
87
- import { isSubagentExecutionContext } from "./subagent-context.js";
88
- import { sanitizeAvailableToolsSection } from "./system-prompt-sanitizer.js";
89
- import { getPermissionLogContext } from "./tool-input-preview.js";
86
+ } from "./status";
87
+ import { isSubagentExecutionContext } from "./subagent-context";
88
+ import { sanitizeAvailableToolsSection } from "./system-prompt-sanitizer";
89
+ import { getPermissionLogContext } from "./tool-input-preview";
90
90
  import {
91
91
  checkRequestedToolRegistration,
92
92
  getToolNameFromValue,
93
- } from "./tool-registry.js";
93
+ } from "./tool-registry";
94
94
  import {
95
95
  canResolveAskPermissionRequest,
96
96
  shouldAutoApprovePermissionState,
97
- } from "./yolo-mode.js";
97
+ } from "./yolo-mode";
98
98
 
99
99
  const PI_AGENT_DIR = getAgentDir();
100
100
  const SESSIONS_DIR = join(PI_AGENT_DIR, "sessions");
package/src/logging.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  LOGS_DIR,
8
8
  PERMISSION_REVIEW_LOG_PATH,
9
9
  type PermissionSystemExtensionConfig,
10
- } from "./extension-config.js";
10
+ } from "./extension-config";
11
11
 
12
12
  export function safeJsonStringify(value: unknown): string | undefined {
13
13
  const seen = new WeakSet<object>();
@@ -1,6 +1,6 @@
1
1
  import { join } from "node:path";
2
2
 
3
- import type { PermissionDecisionState } from "./permission-dialog.js";
3
+ import type { PermissionDecisionState } from "./permission-dialog";
4
4
 
5
5
  export const PERMISSION_FORWARDING_POLL_INTERVAL_MS = 250;
6
6
  export const PERMISSION_FORWARDING_TIMEOUT_MS = 10 * 60 * 1000;
@@ -2,16 +2,16 @@ import { existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { getAgentDir } from "@mariozechner/pi-coding-agent";
4
4
 
5
- import { BashFilter } from "./bash-filter.js";
5
+ import { BashFilter } from "./bash-filter";
6
6
  import {
7
7
  extractFrontmatter,
8
8
  getNonEmptyString,
9
9
  isPermissionState,
10
10
  parseSimpleYamlMap,
11
11
  toRecord,
12
- } from "./common.js";
13
- import { loadUnifiedConfig, stripJsonComments } from "./config-loader.js";
14
- import { getGlobalConfigPath } from "./config-paths.js";
12
+ } from "./common";
13
+ import { loadUnifiedConfig, stripJsonComments } from "./config-loader";
14
+ import { getGlobalConfigPath } from "./config-paths";
15
15
  import type {
16
16
  AgentPermissions,
17
17
  BashPermissions,
@@ -19,13 +19,13 @@ import type {
19
19
  PermissionCheckResult,
20
20
  PermissionDefaultPolicy,
21
21
  PermissionState,
22
- } from "./types.js";
22
+ } from "./types";
23
23
  import {
24
24
  type CompiledWildcardPattern,
25
25
  compileWildcardPatternEntries,
26
26
  findCompiledWildcardMatch,
27
27
  findCompiledWildcardMatchForNames,
28
- } from "./wildcard-matcher.js";
28
+ } from "./wildcard-matcher";
29
29
 
30
30
  function defaultGlobalConfigPath(): string {
31
31
  return getGlobalConfigPath(getAgentDir());
@@ -1,6 +1,6 @@
1
- import type { SkillPromptEntry } from "./skill-prompt-sanitizer.js";
2
- import { formatToolInputForPrompt } from "./tool-input-preview.js";
3
- import type { PermissionCheckResult } from "./types.js";
1
+ import type { SkillPromptEntry } from "./skill-prompt-sanitizer";
2
+ import { formatToolInputForPrompt } from "./tool-input-preview";
3
+ import type { PermissionCheckResult } from "./types";
4
4
 
5
5
  export function formatMissingToolNameReason(): string {
6
6
  return "Tool call was blocked because no tool name was provided. Use a registered tool name from pi.getAllTools().";
@@ -1,8 +1,8 @@
1
1
  import { homedir } from "node:os";
2
2
  import { dirname, join, normalize, resolve, sep } from "node:path";
3
3
 
4
- import type { PermissionManager } from "./permission-manager.js";
5
- import type { PermissionState } from "./types.js";
4
+ import type { PermissionManager } from "./permission-manager";
5
+ import type { PermissionState } from "./types";
6
6
 
7
7
  const AVAILABLE_SKILLS_OPEN_TAG = "<available_skills>";
8
8
  const AVAILABLE_SKILLS_CLOSE_TAG = "</available_skills>";
package/src/status.ts CHANGED
@@ -6,8 +6,8 @@ import type {
6
6
  import {
7
7
  EXTENSION_ID,
8
8
  type PermissionSystemExtensionConfig,
9
- } from "./extension-config.js";
10
- import { isYoloModeEnabled } from "./yolo-mode.js";
9
+ } from "./extension-config";
10
+ import { isYoloModeEnabled } from "./yolo-mode";
11
11
 
12
12
  export const PERMISSION_SYSTEM_STATUS_KEY = EXTENSION_ID;
13
13
  export const PERMISSION_SYSTEM_YOLO_STATUS_VALUE = "yolo";
@@ -1,7 +1,7 @@
1
1
  import { normalize } from "node:path";
2
2
  import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
3
3
 
4
- import { SUBAGENT_ENV_HINT_KEYS } from "./permission-forwarding.js";
4
+ import { SUBAGENT_ENV_HINT_KEYS } from "./permission-forwarding";
5
5
 
6
6
  export function normalizeFilesystemPath(pathValue: string): string {
7
7
  const normalizedPath = normalize(pathValue);
@@ -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,6 +1,6 @@
1
- import { getNonEmptyString, toRecord } from "./common.js";
2
- import { safeJsonStringify } from "./logging.js";
3
- import type { PermissionCheckResult } from "./types.js";
1
+ import { getNonEmptyString, toRecord } from "./common";
2
+ import { safeJsonStringify } from "./logging";
3
+ import type { PermissionCheckResult } from "./types";
4
4
 
5
5
  export const TOOL_INPUT_PREVIEW_MAX_LENGTH = 200;
6
6
  export const TOOL_INPUT_LOG_PREVIEW_MAX_LENGTH = 1000;
@@ -1,4 +1,4 @@
1
- import { getNonEmptyString, toRecord } from "./common.js";
1
+ import { getNonEmptyString, toRecord } from "./common";
2
2
 
3
3
  export type ToolRegistrationCheckResult =
4
4
  | {
package/src/yolo-mode.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { PermissionSystemExtensionConfig } from "./extension-config.js";
2
- import type { PermissionState } from "./types.js";
1
+ import type { PermissionSystemExtensionConfig } from "./extension-config";
2
+ import type { PermissionState } from "./types";
3
3
 
4
4
  export interface AskPermissionResolutionOptions {
5
5
  config: PermissionSystemExtensionConfig;
@@ -5,7 +5,7 @@ import {
5
5
  getActiveAgentName,
6
6
  getActiveAgentNameFromSystemPrompt,
7
7
  normalizeAgentName,
8
- } from "../src/active-agent.js";
8
+ } from "../src/active-agent";
9
9
 
10
10
  afterEach(() => {
11
11
  vi.restoreAllMocks();
@@ -1,6 +1,6 @@
1
1
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
2
 
3
- import type { PermissionState } from "../src/types.js";
3
+ import type { PermissionState } from "../src/types";
4
4
 
5
5
  // Mock wildcard-matcher before importing the module under test.
6
6
  vi.mock("../src/wildcard-matcher.js", () => ({
@@ -14,11 +14,11 @@ vi.mock("../src/wildcard-matcher.js", () => ({
14
14
  findCompiledWildcardMatch: vi.fn(),
15
15
  }));
16
16
 
17
- import { BashFilter } from "../src/bash-filter.js";
17
+ import { BashFilter } from "../src/bash-filter";
18
18
  import {
19
19
  compileWildcardPatterns,
20
20
  findCompiledWildcardMatch,
21
- } from "../src/wildcard-matcher.js";
21
+ } from "../src/wildcard-matcher";
22
22
 
23
23
  const mockedCompilePatterns = vi.mocked(compileWildcardPatterns);
24
24
  const mockedFindMatch = vi.mocked(findCompiledWildcardMatch);
@@ -6,7 +6,7 @@ import {
6
6
  isPermissionState,
7
7
  parseSimpleYamlMap,
8
8
  toRecord,
9
- } from "../src/common.js";
9
+ } from "../src/common";
10
10
 
11
11
  afterEach(() => {
12
12
  vi.restoreAllMocks();
@@ -7,7 +7,7 @@ import {
7
7
  loadAndMergeConfigs,
8
8
  loadUnifiedConfig,
9
9
  mergeUnifiedConfigs,
10
- } from "../src/config-loader.js";
10
+ } from "../src/config-loader";
11
11
 
12
12
  describe("loadUnifiedConfig", () => {
13
13
  let tempDir: string;
@@ -3,13 +3,13 @@ import { mkdtempSync, readFileSync, rmSync } 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
- import { registerPermissionSystemCommand } from "../src/config-modal.js";
6
+ import { registerPermissionSystemCommand } from "../src/config-modal";
7
7
  import {
8
8
  DEFAULT_EXTENSION_CONFIG,
9
9
  loadPermissionSystemConfig,
10
10
  type PermissionSystemExtensionConfig,
11
11
  savePermissionSystemConfig,
12
- } from "../src/extension-config.js";
12
+ } from "../src/extension-config";
13
13
 
14
14
  vi.mock("@mariozechner/pi-coding-agent", () => ({
15
15
  getSettingsListTheme: () => ({}),
@@ -11,7 +11,7 @@ import {
11
11
  getLegacyProjectPolicyPath,
12
12
  getProjectConfigPath,
13
13
  REVIEW_LOG_FILENAME,
14
- } from "../src/config-paths.js";
14
+ } from "../src/config-paths";
15
15
 
16
16
  describe("config-paths", () => {
17
17
  const agentDir = "/home/user/.pi/agent";
@@ -9,10 +9,10 @@ import {
9
9
  import { tmpdir } from "node:os";
10
10
  import { join } from "node:path";
11
11
  import { test } from "vitest";
12
- import { buildResolvedConfigLogEntry } from "../src/config-reporter.js";
13
- import { createPermissionSystemLogger } from "../src/logging.js";
14
- import type { ResolvedPolicyPaths } from "../src/permission-manager.js";
15
- import { PermissionManager } from "../src/permission-manager.js";
12
+ import { buildResolvedConfigLogEntry } from "../src/config-reporter";
13
+ import { createPermissionSystemLogger } from "../src/logging";
14
+ import type { ResolvedPolicyPaths } from "../src/permission-manager";
15
+ import { PermissionManager } from "../src/permission-manager";
16
16
 
17
17
  test("buildResolvedConfigLogEntry includes policy paths and legacy detection flags", () => {
18
18
  const policyPaths: ResolvedPolicyPaths = {
@@ -7,7 +7,7 @@ import {
7
7
  detectMisplacedPermissionKeys,
8
8
  loadPermissionSystemConfig,
9
9
  normalizePermissionSystemConfig,
10
- } from "../src/extension-config.js";
10
+ } from "../src/extension-config";
11
11
 
12
12
  describe("detectMisplacedPermissionKeys", () => {
13
13
  it("returns an empty array for a record with only valid extension keys", () => {
@@ -20,7 +20,7 @@ import {
20
20
  isPathWithinDirectory,
21
21
  normalizePathForComparison,
22
22
  PATH_BEARING_TOOLS,
23
- } from "../src/external-directory.js";
23
+ } from "../src/external-directory";
24
24
 
25
25
  afterEach(() => {
26
26
  vi.restoreAllMocks();
@@ -15,10 +15,10 @@ import {
15
15
  formatSkillPathDenyReason,
16
16
  formatUnknownToolReason,
17
17
  formatUserDeniedReason,
18
- } from "../src/permission-prompts.js";
19
- import type { SkillPromptEntry } from "../src/skill-prompt-sanitizer.js";
20
- import { formatToolInputForPrompt } from "../src/tool-input-preview.js";
21
- import type { PermissionCheckResult } from "../src/types.js";
18
+ } from "../src/permission-prompts";
19
+ import type { SkillPromptEntry } from "../src/skill-prompt-sanitizer";
20
+ import { formatToolInputForPrompt } from "../src/tool-input-preview";
21
+ import type { PermissionCheckResult } from "../src/types";
22
22
 
23
23
  const mockedFormatToolInput = vi.mocked(formatToolInputForPrompt);
24
24
 
@@ -10,51 +10,51 @@ import {
10
10
  import { tmpdir } from "node:os";
11
11
  import { dirname, join, resolve } from "node:path";
12
12
  import { test } from "vitest";
13
- import { BashFilter } from "../src/bash-filter.js";
13
+ import { BashFilter } from "../src/bash-filter";
14
14
  import {
15
15
  createActiveToolsCacheKey,
16
16
  createBeforeAgentStartPromptStateKey,
17
17
  shouldApplyCachedAgentStartState,
18
- } from "../src/before-agent-start-cache.js";
19
- import { getGlobalConfigPath } from "../src/config-paths.js";
18
+ } from "../src/before-agent-start-cache";
19
+ import { getGlobalConfigPath } from "../src/config-paths";
20
20
  import {
21
21
  DEFAULT_EXTENSION_CONFIG,
22
22
  loadPermissionSystemConfig,
23
23
  savePermissionSystemConfig,
24
- } from "../src/extension-config.js";
25
- import piPermissionSystemExtension from "../src/index.js";
26
- import { createPermissionSystemLogger } from "../src/logging.js";
24
+ } from "../src/extension-config";
25
+ import piPermissionSystemExtension from "../src/index";
26
+ import { createPermissionSystemLogger } from "../src/logging";
27
27
  import {
28
28
  createPermissionForwardingLocation,
29
29
  isForwardedPermissionRequestForSession,
30
30
  resolvePermissionForwardingTargetSessionId,
31
31
  SUBAGENT_ENV_HINT_KEYS,
32
32
  SUBAGENT_PARENT_SESSION_ENV_KEY,
33
- } from "../src/permission-forwarding.js";
33
+ } from "../src/permission-forwarding";
34
34
  import {
35
35
  normalizeRawPermission,
36
36
  PermissionManager,
37
- } from "../src/permission-manager.js";
37
+ } from "../src/permission-manager";
38
38
  import {
39
39
  findSkillPathMatch,
40
40
  parseAllSkillPromptSections,
41
41
  resolveSkillPromptEntries,
42
- } from "../src/skill-prompt-sanitizer.js";
43
- import { getPermissionSystemStatus } from "../src/status.js";
44
- import { sanitizeAvailableToolsSection } from "../src/system-prompt-sanitizer.js";
42
+ } from "../src/skill-prompt-sanitizer";
43
+ import { getPermissionSystemStatus } from "../src/status";
44
+ import { sanitizeAvailableToolsSection } from "../src/system-prompt-sanitizer";
45
45
  import {
46
46
  checkRequestedToolRegistration,
47
47
  getToolNameFromValue,
48
- } from "../src/tool-registry.js";
48
+ } from "../src/tool-registry";
49
49
  import type {
50
50
  AgentPermissions,
51
51
  GlobalPermissionConfig,
52
52
  PermissionState,
53
- } from "../src/types.js";
53
+ } from "../src/types";
54
54
  import {
55
55
  canResolveAskPermissionRequest,
56
56
  shouldAutoApprovePermissionState,
57
- } from "../src/yolo-mode.js";
57
+ } from "../src/yolo-mode";
58
58
 
59
59
  type CreateManagerOptions = {
60
60
  mcpServerNames?: readonly string[];
@@ -2,10 +2,10 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { dirname, join } from "node:path";
4
4
  import { afterEach, beforeEach, describe, expect, test } from "vitest";
5
- import { getGlobalConfigPath } from "../src/config-paths.js";
6
- import { DEFAULT_EXTENSION_CONFIG } from "../src/extension-config.js";
7
- import piPermissionSystemExtension from "../src/index.js";
8
- import type { GlobalPermissionConfig } from "../src/types.js";
5
+ import { getGlobalConfigPath } from "../src/config-paths";
6
+ import { DEFAULT_EXTENSION_CONFIG } from "../src/extension-config";
7
+ import piPermissionSystemExtension from "../src/index";
8
+ import type { GlobalPermissionConfig } from "../src/types";
9
9
 
10
10
  type MockHandler = (
11
11
  event: Record<string, unknown>,
@@ -1,10 +1,10 @@
1
1
  import { afterEach, describe, expect, test, vi } from "vitest";
2
- import type { PermissionManager } from "../src/permission-manager.js";
2
+ import type { PermissionManager } from "../src/permission-manager";
3
3
  import {
4
4
  findSkillPathMatch,
5
5
  resolveSkillPromptEntries,
6
- } from "../src/skill-prompt-sanitizer.js";
7
- import type { PermissionCheckResult } from "../src/types.js";
6
+ } from "../src/skill-prompt-sanitizer";
7
+ import type { PermissionCheckResult } from "../src/types";
8
8
 
9
9
  afterEach(() => {
10
10
  vi.restoreAllMocks();
@@ -1,10 +1,10 @@
1
1
  import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
  import { afterEach, describe, expect, test, vi } from "vitest";
3
- import { SUBAGENT_ENV_HINT_KEYS } from "../src/permission-forwarding.js";
3
+ import { SUBAGENT_ENV_HINT_KEYS } from "../src/permission-forwarding";
4
4
  import {
5
5
  isSubagentExecutionContext,
6
6
  normalizeFilesystemPath,
7
- } from "../src/subagent-context.js";
7
+ } from "../src/subagent-context";
8
8
 
9
9
  afterEach(() => {
10
10
  vi.unstubAllEnvs();
@@ -1,6 +1,6 @@
1
1
  import { afterEach, describe, expect, test, vi } from "vitest";
2
2
 
3
- import { sanitizeAvailableToolsSection } from "../src/system-prompt-sanitizer.js";
3
+ import { sanitizeAvailableToolsSection } from "../src/system-prompt-sanitizer";
4
4
 
5
5
  afterEach(() => {
6
6
  vi.restoreAllMocks();
@@ -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
+ });
@@ -5,7 +5,7 @@ vi.mock("../src/logging.js", () => ({
5
5
  safeJsonStringify: vi.fn((value: unknown) => JSON.stringify(value)),
6
6
  }));
7
7
 
8
- import { safeJsonStringify } from "../src/logging.js";
8
+ import { safeJsonStringify } from "../src/logging";
9
9
  import {
10
10
  countTextLines,
11
11
  formatCount,
@@ -24,8 +24,8 @@ import {
24
24
  TOOL_INPUT_PREVIEW_MAX_LENGTH,
25
25
  TOOL_TEXT_SUMMARY_MAX_LENGTH,
26
26
  truncateInlineText,
27
- } from "../src/tool-input-preview.js";
28
- import type { PermissionCheckResult } from "../src/types.js";
27
+ } from "../src/tool-input-preview";
28
+ import type { PermissionCheckResult } from "../src/types";
29
29
 
30
30
  const mockedStringify = vi.mocked(safeJsonStringify);
31
31
 
@@ -3,7 +3,7 @@ import { afterEach, describe, expect, test, vi } from "vitest";
3
3
  import {
4
4
  checkRequestedToolRegistration,
5
5
  getToolNameFromValue,
6
- } from "../src/tool-registry.js";
6
+ } from "../src/tool-registry";
7
7
 
8
8
  afterEach(() => {
9
9
  vi.restoreAllMocks();
@@ -5,7 +5,7 @@ import {
5
5
  compileWildcardPatternEntries,
6
6
  findCompiledWildcardMatch,
7
7
  findCompiledWildcardMatchForNames,
8
- } from "../src/wildcard-matcher.js";
8
+ } from "../src/wildcard-matcher";
9
9
 
10
10
  afterEach(() => {
11
11
  vi.restoreAllMocks();
@@ -1,9 +1,9 @@
1
1
  import { afterEach, describe, expect, test, vi } from "vitest";
2
- import type { PermissionSystemExtensionConfig } from "../src/extension-config.js";
2
+ import type { PermissionSystemExtensionConfig } from "../src/extension-config";
3
3
  import {
4
4
  canResolveAskPermissionRequest,
5
5
  shouldAutoApprovePermissionState,
6
- } from "../src/yolo-mode.js";
6
+ } from "../src/yolo-mode";
7
7
 
8
8
  afterEach(() => {
9
9
  vi.restoreAllMocks();