@gotgenes/pi-permission-system 7.1.3 → 7.2.0

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 (98) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/package.json +7 -3
  3. package/src/active-agent.ts +1 -1
  4. package/src/bash-arity.ts +1 -0
  5. package/src/config-modal.ts +2 -0
  6. package/src/forwarded-permissions/io.ts +4 -2
  7. package/src/forwarded-permissions/polling.ts +8 -7
  8. package/src/handlers/before-agent-start.ts +7 -6
  9. package/src/handlers/gates/bash-external-directory.ts +4 -4
  10. package/src/handlers/gates/bash-path-extractor.ts +4 -6
  11. package/src/handlers/gates/bash-path.ts +5 -5
  12. package/src/handlers/gates/descriptor.ts +6 -6
  13. package/src/handlers/gates/external-directory.ts +2 -2
  14. package/src/handlers/gates/helpers.ts +2 -2
  15. package/src/handlers/gates/path.ts +4 -4
  16. package/src/handlers/gates/runner.ts +7 -4
  17. package/src/handlers/gates/skill-read.ts +5 -5
  18. package/src/handlers/gates/tool.ts +5 -5
  19. package/src/handlers/lifecycle.ts +9 -8
  20. package/src/handlers/permission-gate-handler.ts +12 -7
  21. package/src/logging.ts +3 -0
  22. package/src/node-modules-discovery.ts +1 -1
  23. package/src/normalize.ts +1 -0
  24. package/src/permission-event-rpc.ts +2 -0
  25. package/src/permission-manager.ts +7 -6
  26. package/src/permission-merge.ts +4 -2
  27. package/src/permission-prompter.ts +3 -0
  28. package/src/permission-prompts.ts +1 -1
  29. package/src/policy-loader.ts +5 -5
  30. package/src/service.ts +1 -0
  31. package/src/skill-prompt-sanitizer.ts +3 -3
  32. package/src/tool-registry.ts +1 -1
  33. package/src/wildcard-matcher.ts +2 -2
  34. package/src/yolo-mode.ts +2 -1
  35. package/{tests → test}/active-agent.test.ts +1 -1
  36. package/{tests → test}/bash-arity.test.ts +4 -4
  37. package/{tests → test}/bash-external-directory.test.ts +3 -3
  38. package/{tests → test}/common.test.ts +1 -1
  39. package/{tests → test}/config-loader.test.ts +1 -1
  40. package/{tests → test}/config-modal.test.ts +9 -11
  41. package/{tests → test}/config-paths.test.ts +1 -1
  42. package/{tests → test}/config-reporter.test.ts +4 -4
  43. package/{tests → test}/denial-messages.test.ts +2 -2
  44. package/{tests → test}/expand-home.test.ts +1 -1
  45. package/{tests → test}/extension-config.test.ts +1 -1
  46. package/{tests → test}/extension-paths.test.ts +2 -2
  47. package/{tests → test}/forwarded-permissions/io.test.ts +2 -2
  48. package/{tests → test}/forwarding-manager.test.ts +1 -1
  49. package/{tests → test}/handlers/before-agent-start.test.ts +5 -5
  50. package/{tests → test}/handlers/external-directory-integration.test.ts +8 -8
  51. package/{tests → test}/handlers/external-directory-session-dedup.test.ts +6 -6
  52. package/{tests → test}/handlers/gates/bash-external-directory.test.ts +5 -8
  53. package/{tests → test}/handlers/gates/bash-path.test.ts +6 -9
  54. package/{tests → test}/handlers/gates/external-directory-messages.test.ts +1 -1
  55. package/{tests → test}/handlers/gates/external-directory.test.ts +4 -7
  56. package/{tests → test}/handlers/gates/helpers.test.ts +1 -1
  57. package/{tests → test}/handlers/gates/path.test.ts +6 -6
  58. package/{tests → test}/handlers/gates/runner.test.ts +5 -5
  59. package/{tests → test}/handlers/gates/skill-read.test.ts +12 -14
  60. package/{tests → test}/handlers/gates/tool.test.ts +4 -4
  61. package/{tests → test}/handlers/input-events.test.ts +7 -7
  62. package/{tests → test}/handlers/input.test.ts +5 -5
  63. package/{tests → test}/handlers/lifecycle.test.ts +2 -2
  64. package/{tests → test}/handlers/tool-call-events.test.ts +7 -7
  65. package/{tests → test}/handlers/tool-call.test.ts +5 -5
  66. package/{tests → test}/input-normalizer.test.ts +2 -2
  67. package/{tests → test}/mcp-targets.test.ts +1 -1
  68. package/{tests → test}/node-modules-discovery.test.ts +1 -1
  69. package/{tests → test}/normalize.test.ts +1 -1
  70. package/{tests → test}/path-utils.test.ts +1 -1
  71. package/{tests → test}/pattern-suggest.test.ts +1 -1
  72. package/{tests → test}/permission-dialog.test.ts +1 -1
  73. package/{tests → test}/permission-event-rpc.test.ts +4 -3
  74. package/{tests → test}/permission-events.test.ts +6 -4
  75. package/{tests → test}/permission-forwarding.test.ts +2 -1
  76. package/{tests → test}/permission-gate.test.ts +2 -2
  77. package/{tests → test}/permission-manager-unified.test.ts +9 -7
  78. package/{tests → test}/permission-merge.test.ts +1 -1
  79. package/{tests → test}/permission-prompter.test.ts +4 -4
  80. package/{tests → test}/permission-prompts.test.ts +4 -4
  81. package/{tests → test}/permission-session.test.ts +9 -9
  82. package/{tests → test}/permission-system.test.ts +21 -21
  83. package/{tests → test}/pi-infrastructure-read.test.ts +2 -2
  84. package/{tests → test}/policy-loader.test.ts +1 -1
  85. package/{tests → test}/rule.test.ts +2 -2
  86. package/{tests → test}/runtime.test.ts +4 -4
  87. package/{tests → test}/service.test.ts +4 -4
  88. package/{tests → test}/session-logger.test.ts +2 -2
  89. package/{tests → test}/session-rules.test.ts +2 -2
  90. package/{tests → test}/session-start.test.ts +4 -4
  91. package/{tests → test}/skill-prompt-sanitizer.test.ts +3 -3
  92. package/{tests → test}/subagent-context.test.ts +2 -2
  93. package/{tests → test}/synthesize.test.ts +3 -3
  94. package/{tests → test}/system-prompt-sanitizer.test.ts +1 -1
  95. package/{tests → test}/tool-input-preview.test.ts +3 -3
  96. package/{tests → test}/tool-registry.test.ts +1 -1
  97. package/{tests → test}/wildcard-matcher.test.ts +1 -1
  98. package/{tests → test}/yolo-mode.test.ts +2 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ 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
+ ## [7.2.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.1.4...pi-permission-system-v7.2.0) (2026-05-24)
9
+
10
+
11
+ ### Features
12
+
13
+ * add eslint config with type-aware rules and import enforcement ([4fb3cc6](https://github.com/gotgenes/pi-packages/commit/4fb3cc678da10d350b85c464318476ba9ae99dca))
14
+
15
+ ## [7.1.4](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.1.3...pi-permission-system-v7.1.4) (2026-05-23)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * add package.json imports field for #src/#test path aliases ([#157](https://github.com/gotgenes/pi-packages/issues/157)) ([75b4598](https://github.com/gotgenes/pi-packages/commit/75b45980810583452f7741678359c004900c8bd0))
21
+
8
22
  ## [7.1.3](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.1.2...pi-permission-system-v7.1.3) (2026-05-23)
9
23
 
10
24
 
package/package.json CHANGED
@@ -1,14 +1,18 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "7.1.3",
3
+ "version": "7.2.0",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./src/service.ts"
8
8
  },
9
+ "imports": {
10
+ "#src/*": "./src/*",
11
+ "#test/*": "./test/*"
12
+ },
9
13
  "files": [
10
14
  "src",
11
- "tests",
15
+ "test",
12
16
  "config/config.example.json",
13
17
  "schemas/permissions.schema.json",
14
18
  "README.md",
@@ -73,6 +77,6 @@
73
77
  "test": "vitest run",
74
78
  "test:watch": "vitest",
75
79
  "lint:md": "rumdl check *.md docs/**/*.md",
76
- "lint": "biome check . && pnpm run lint:md"
80
+ "lint": "biome check . && eslint . && pnpm run lint:md"
77
81
  }
78
82
  }
@@ -49,7 +49,7 @@ export function getActiveAgentNameFromSystemPrompt(
49
49
  return null;
50
50
  }
51
51
 
52
- const match = systemPrompt.match(ACTIVE_AGENT_TAG_REGEX);
52
+ const match = ACTIVE_AGENT_TAG_REGEX.exec(systemPrompt);
53
53
  if (!match?.[1]) {
54
54
  return null;
55
55
  }
package/src/bash-arity.ts CHANGED
@@ -175,6 +175,7 @@ export function prefix(tokens: string[]): string[] {
175
175
  .map((t) => t.toLowerCase())
176
176
  .join(" ");
177
177
  const arity = ARITY[key];
178
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ARITY record type hides that a key may be absent at runtime
178
179
  if (arity !== undefined) {
179
180
  return tokens.slice(0, Math.min(arity, tokens.length));
180
181
  }
@@ -61,6 +61,7 @@ function toOnOff(value: boolean): string {
61
61
  }
62
62
 
63
63
  function formatRulesSummary(rules: Ruleset): string {
64
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- origin may be absent despite its type
64
65
  const configRules = rules.filter((r) => r.layer === "config" && r.origin);
65
66
  if (configRules.length === 0) return "";
66
67
  const formatted = configRules
@@ -171,6 +172,7 @@ async function openSettingsModal(
171
172
  margin: 1,
172
173
  };
173
174
 
175
+ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- ctx.ui.custom<void> is valid; rule does not allow void in generic fn call type args
174
176
  await ctx.ui.custom<void>(
175
177
  (_tui, _theme, _keybindings, done) => {
176
178
  let current = controller.getConfig();
@@ -9,13 +9,13 @@ import {
9
9
  writeFileSync,
10
10
  } from "node:fs";
11
11
 
12
- import { isPermissionDecisionState } from "../permission-dialog";
12
+ import { isPermissionDecisionState } from "#src/permission-dialog";
13
13
  import {
14
14
  createPermissionForwardingLocation,
15
15
  type ForwardedPermissionRequest,
16
16
  type ForwardedPermissionResponse,
17
17
  type PermissionForwardingLocation,
18
- } from "../permission-forwarding";
18
+ } from "#src/permission-forwarding";
19
19
 
20
20
  type LogFn = (event: string, details: Record<string, unknown>) => void;
21
21
 
@@ -262,6 +262,7 @@ export function readForwardedPermissionRequest(
262
262
  const raw = readFileSync(filePath, "utf-8");
263
263
  const parsed = JSON.parse(raw) as Partial<ForwardedPermissionRequest>;
264
264
  if (
265
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JSON.parse can return null for the string "null"
265
266
  !parsed ||
266
267
  typeof parsed.id !== "string" ||
267
268
  typeof parsed.createdAt !== "number" ||
@@ -303,6 +304,7 @@ export function readForwardedPermissionResponse(
303
304
  const raw = readFileSync(filePath, "utf-8");
304
305
  const parsed = JSON.parse(raw) as Partial<ForwardedPermissionResponse>;
305
306
  if (
307
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JSON.parse can return null for the string "null"
306
308
  !parsed ||
307
309
  typeof parsed.approved !== "boolean" ||
308
310
  !isPermissionDecisionState(parsed.state) ||
@@ -5,12 +5,12 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
5
5
  import {
6
6
  getActiveAgentName,
7
7
  getActiveAgentNameFromSystemPrompt,
8
- } from "../active-agent";
9
- import { toRecord } from "../common";
8
+ } from "#src/active-agent";
9
+ import { toRecord } from "#src/common";
10
10
  import type {
11
11
  PermissionPromptDecision,
12
12
  RequestPermissionOptions,
13
- } from "../permission-dialog";
13
+ } from "#src/permission-dialog";
14
14
  import {
15
15
  type ForwardedPermissionRequest,
16
16
  type ForwardedPermissionResponse,
@@ -19,8 +19,8 @@ import {
19
19
  PERMISSION_FORWARDING_TIMEOUT_MS,
20
20
  resolvePermissionForwardingTargetSessionId,
21
21
  SUBAGENT_PARENT_SESSION_ENV_CANDIDATES,
22
- } from "../permission-forwarding";
23
- import { isSubagentExecutionContext } from "../subagent-context";
22
+ } from "#src/permission-forwarding";
23
+ import { isSubagentExecutionContext } from "#src/subagent-context";
24
24
 
25
25
  import {
26
26
  cleanupPermissionForwardingLocationIfEmpty,
@@ -69,6 +69,7 @@ function getContextSystemPrompt(ctx: ExtensionContext): string | undefined {
69
69
  }
70
70
 
71
71
  try {
72
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- getSystemPrompt is a Pi SDK accessor returning any
72
73
  const systemPrompt = getSystemPrompt.call(ctx);
73
74
  return typeof systemPrompt === "string" ? systemPrompt : undefined;
74
75
  } catch (error) {
@@ -135,8 +136,8 @@ export async function waitForForwardedPermissionApproval(
135
136
 
136
137
  const requestId = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}-${process.pid}`;
137
138
  const requesterAgentName =
138
- getActiveAgentName(ctx) ||
139
- getActiveAgentNameFromSystemPrompt(getContextSystemPrompt(ctx)) ||
139
+ getActiveAgentName(ctx) ??
140
+ getActiveAgentNameFromSystemPrompt(getContextSystemPrompt(ctx)) ??
140
141
  "unknown";
141
142
  const request: ForwardedPermissionRequest = {
142
143
  id: requestId,
@@ -6,12 +6,12 @@ import type {
6
6
  import {
7
7
  createActiveToolsCacheKey,
8
8
  createBeforeAgentStartPromptStateKey,
9
- } from "../before-agent-start-cache";
10
- import type { PermissionSession } from "../permission-session";
11
- import { resolveSkillPromptEntries } from "../skill-prompt-sanitizer";
12
- import { sanitizeAvailableToolsSection } from "../system-prompt-sanitizer";
13
- import { getToolNameFromValue, type ToolRegistry } from "../tool-registry";
14
- import type { PermissionState } from "../types";
9
+ } from "#src/before-agent-start-cache";
10
+ import type { PermissionSession } from "#src/permission-session";
11
+ import { resolveSkillPromptEntries } from "#src/skill-prompt-sanitizer";
12
+ import { sanitizeAvailableToolsSection } from "#src/system-prompt-sanitizer";
13
+ import { getToolNameFromValue, type ToolRegistry } from "#src/tool-registry";
14
+ import type { PermissionState } from "#src/types";
15
15
 
16
16
  /** Minimal subset of BeforeAgentStartEvent used by this handler. */
17
17
  interface BeforeAgentStartPayload {
@@ -45,6 +45,7 @@ export class AgentPrepHandler {
45
45
  private readonly toolRegistry: ToolRegistry,
46
46
  ) {}
47
47
 
48
+ // eslint-disable-next-line @typescript-eslint/require-await
48
49
  async handle(
49
50
  event: BeforeAgentStartPayload,
50
51
  ctx: ExtensionContext,
@@ -1,7 +1,7 @@
1
- import { getNonEmptyString, toRecord } from "../../common";
2
- import type { Rule } from "../../rule";
3
- import { deriveApprovalPattern } from "../../session-rules";
4
- import type { PermissionCheckResult } from "../../types";
1
+ import { getNonEmptyString, toRecord } from "#src/common";
2
+ import type { Rule } from "#src/rule";
3
+ import { deriveApprovalPattern } from "#src/session-rules";
4
+ import type { PermissionCheckResult } from "#src/types";
5
5
  import { extractExternalPathsFromBashCommand } from "./bash-path-extractor";
6
6
  import type { GateResult } from "./descriptor";
7
7
  import { formatBashExternalDirectoryAskPrompt } from "./external-directory-messages";
@@ -4,7 +4,7 @@ import { basename } from "node:path";
4
4
  import {
5
5
  isPathOutsideWorkingDirectory,
6
6
  normalizePathForComparison,
7
- } from "../../path-utils";
7
+ } from "#src/path-utils";
8
8
 
9
9
  // ── tree-sitter-bash lazy parser ───────────────────────────────────────────
10
10
 
@@ -40,13 +40,11 @@ async function initParser(): Promise<TSParser> {
40
40
  const bashWasm = req.resolve("tree-sitter-bash/tree-sitter-bash.wasm");
41
41
  const bash = await Language.load(bashWasm);
42
42
  parser.setLanguage(bash);
43
- return parser as TSParser;
43
+ return parser;
44
44
  }
45
45
 
46
46
  function getParser(): Promise<TSParser> {
47
- if (!parserPromise) {
48
- parserPromise = initParser();
49
- }
47
+ parserPromise ??= initParser();
50
48
  return parserPromise;
51
49
  }
52
50
 
@@ -83,7 +81,7 @@ function resolveNodeText(node: TSNode): string {
83
81
  case "raw_string": {
84
82
  // Strip surrounding single quotes: 'content' → content
85
83
  const t = node.text;
86
- if (t.length >= 2 && t[0] === "'" && t[t.length - 1] === "'") {
84
+ if (t.length >= 2 && t.startsWith("'") && t.endsWith("'")) {
87
85
  return t.slice(1, -1);
88
86
  }
89
87
  return t;
@@ -1,7 +1,7 @@
1
- import { getNonEmptyString, toRecord } from "../../common";
2
- import type { Rule } from "../../rule";
3
- import { deriveApprovalPattern } from "../../session-rules";
4
- import type { PermissionCheckResult } from "../../types";
1
+ import { getNonEmptyString, toRecord } from "#src/common";
2
+ import type { Rule } from "#src/rule";
3
+ import { deriveApprovalPattern } from "#src/session-rules";
4
+ import type { PermissionCheckResult } from "#src/types";
5
5
  import { extractTokensForPathRules } from "./bash-path-extractor";
6
6
  import type { GateResult } from "./descriptor";
7
7
  import { formatPathAskPrompt } from "./path";
@@ -73,7 +73,7 @@ export async function describeBashPathGate(
73
73
  worstToken = token;
74
74
  break; // Short-circuit on deny.
75
75
  }
76
- if (check.state === "ask" && (!worstCheck || worstCheck.state !== "ask")) {
76
+ if (check.state === "ask" && worstCheck?.state !== "ask") {
77
77
  worstCheck = check;
78
78
  worstToken = token;
79
79
  }
@@ -1,9 +1,9 @@
1
- import type { DenialContext } from "../../denial-messages";
2
- import type { PermissionPromptDecision } from "../../permission-dialog";
3
- import type { PermissionDecisionEvent } from "../../permission-events";
4
- import type { PromptPermissionDetails } from "../../permission-prompter";
5
- import type { Rule } from "../../rule";
6
- import type { PermissionCheckResult, PermissionState } from "../../types";
1
+ import type { DenialContext } from "#src/denial-messages";
2
+ import type { PermissionPromptDecision } from "#src/permission-dialog";
3
+ import type { PermissionDecisionEvent } from "#src/permission-events";
4
+ import type { PromptPermissionDetails } from "#src/permission-prompter";
5
+ import type { Rule } from "#src/rule";
6
+ import type { PermissionCheckResult, PermissionState } from "#src/types";
7
7
 
8
8
  // ── Descriptor types ───────────────────────────────────────────────────────
9
9
 
@@ -3,8 +3,8 @@ import {
3
3
  isPathOutsideWorkingDirectory,
4
4
  isPiInfrastructureRead,
5
5
  normalizePathForComparison,
6
- } from "../../path-utils";
7
- import { deriveApprovalPattern } from "../../session-rules";
6
+ } from "#src/path-utils";
7
+ import { deriveApprovalPattern } from "#src/session-rules";
8
8
  import type { GateResult } from "./descriptor";
9
9
  import { formatExternalDirectoryAskPrompt } from "./external-directory-messages";
10
10
  import type { ToolCallContext } from "./types";
@@ -1,5 +1,5 @@
1
- import type { PermissionDecisionResolution } from "../../permission-events";
2
- import type { PermissionCheckResult } from "../../types";
1
+ import type { PermissionDecisionResolution } from "#src/permission-events";
2
+ import type { PermissionCheckResult } from "#src/types";
3
3
 
4
4
  /**
5
5
  * Derive the human-readable value for a decision event from a check result.
@@ -1,7 +1,7 @@
1
- import { getPathBearingToolPath } from "../../path-utils";
2
- import type { Rule } from "../../rule";
3
- import { deriveApprovalPattern } from "../../session-rules";
4
- import type { PermissionCheckResult } from "../../types";
1
+ import { getPathBearingToolPath } from "#src/path-utils";
2
+ import type { Rule } from "#src/rule";
3
+ import { deriveApprovalPattern } from "#src/session-rules";
4
+ import type { PermissionCheckResult } from "#src/types";
5
5
  import type { GateDescriptor, GateResult } from "./descriptor";
6
6
  import type { ToolCallContext } from "./types";
7
7
 
@@ -2,10 +2,10 @@ import {
2
2
  formatDenyReason,
3
3
  formatUnavailableReason,
4
4
  formatUserDeniedReason,
5
- } from "../../denial-messages";
6
- import type { PermissionPromptDecision } from "../../permission-dialog";
7
- import { applyPermissionGate } from "../../permission-gate";
8
- import type { PermissionCheckResult } from "../../types";
5
+ } from "#src/denial-messages";
6
+ import type { PermissionPromptDecision } from "#src/permission-dialog";
7
+ import { applyPermissionGate } from "#src/permission-gate";
8
+ import type { PermissionCheckResult } from "#src/types";
9
9
  import type { GateDescriptor, GateRunnerDeps } from "./descriptor";
10
10
  import { deriveResolution } from "./helpers";
11
11
  import type { GateOutcome } from "./types";
@@ -61,6 +61,7 @@ export async function runGateCheck(
61
61
  value: descriptor.decision.value,
62
62
  result: "allow",
63
63
  resolution: "session_approved",
64
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ?? null normalises undefined to null for the log record
64
65
  origin: check.origin ?? null,
65
66
  agentName: agentName ?? null,
66
67
  matchedPattern: check.matchedPattern ?? null,
@@ -107,6 +108,7 @@ export async function runGateCheck(
107
108
  autoApproved = decision.autoApproved === true;
108
109
  return decision;
109
110
  },
111
+ // eslint-disable-next-line @typescript-eslint/unbound-method -- logger methods are plain functions; no this-binding issue
110
112
  writeLog: deps.writeReviewLog,
111
113
  logContext: { ...descriptor.logContext, agentName },
112
114
  messages,
@@ -128,6 +130,7 @@ export async function runGateCheck(
128
130
  canConfirm,
129
131
  autoApproved,
130
132
  ),
133
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ?? null normalises undefined to null for the log record
131
134
  origin: check.origin ?? null,
132
135
  agentName: agentName ?? null,
133
136
  matchedPattern: check.matchedPattern ?? null,
@@ -1,8 +1,8 @@
1
- import { toRecord } from "../../common";
2
- import { normalizePathForComparison } from "../../path-utils";
3
- import { formatSkillPathAskPrompt } from "../../permission-prompts";
4
- import type { SkillPromptEntry } from "../../skill-prompt-sanitizer";
5
- import { findSkillPathMatch } from "../../skill-prompt-sanitizer";
1
+ import { toRecord } from "#src/common";
2
+ import { normalizePathForComparison } from "#src/path-utils";
3
+ import { formatSkillPathAskPrompt } from "#src/permission-prompts";
4
+ import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
5
+ import { findSkillPathMatch } from "#src/skill-prompt-sanitizer";
6
6
  import type { GateDescriptor } from "./descriptor";
7
7
  import type { ToolCallContext } from "./types";
8
8
 
@@ -1,8 +1,8 @@
1
- import { getPathBearingToolPath, PATH_BEARING_TOOLS } from "../../path-utils";
2
- import { suggestSessionPattern } from "../../pattern-suggest";
3
- import { formatAskPrompt } from "../../permission-prompts";
4
- import { getPermissionLogContext } from "../../tool-input-preview";
5
- import type { PermissionCheckResult } from "../../types";
1
+ import { getPathBearingToolPath, PATH_BEARING_TOOLS } from "#src/path-utils";
2
+ import { suggestSessionPattern } from "#src/pattern-suggest";
3
+ import { formatAskPrompt } from "#src/permission-prompts";
4
+ import { getPermissionLogContext } from "#src/tool-input-preview";
5
+ import type { PermissionCheckResult } from "#src/types";
6
6
  import type { GateDescriptor } from "./descriptor";
7
7
  import { deriveDecisionValue } from "./helpers";
8
8
  import type { ToolCallContext } from "./types";
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
 
3
- import type { PermissionSession } from "../permission-session";
4
- import { PERMISSION_SYSTEM_STATUS_KEY } from "../status";
3
+ import type { PermissionSession } from "#src/permission-session";
4
+ import { PERMISSION_SYSTEM_STATUS_KEY } from "#src/status";
5
5
 
6
6
  /** Minimal subset of SessionStartEvent used by this handler. */
7
7
  interface SessionStartPayload {
@@ -26,7 +26,7 @@ export class SessionLifecycleHandler {
26
26
  private readonly cleanupRpc: () => void,
27
27
  ) {}
28
28
 
29
- async handleSessionStart(
29
+ handleSessionStart(
30
30
  event: SessionStartPayload,
31
31
  ctx: ExtensionContext,
32
32
  ): Promise<void> {
@@ -48,13 +48,12 @@ export class SessionLifecycleHandler {
48
48
  cwd: ctx.cwd,
49
49
  });
50
50
  }
51
+ return Promise.resolve();
51
52
  }
52
53
 
53
- async handleResourcesDiscover(
54
- event: ResourcesDiscoverPayload,
55
- ): Promise<void> {
54
+ handleResourcesDiscover(event: ResourcesDiscoverPayload): Promise<void> {
56
55
  if (event.reason !== "reload") {
57
- return;
56
+ return Promise.resolve();
58
57
  }
59
58
 
60
59
  const { session } = this;
@@ -64,9 +63,10 @@ export class SessionLifecycleHandler {
64
63
  reason: event.reason,
65
64
  cwd: session.getRuntimeContext()?.cwd ?? null,
66
65
  });
66
+ return Promise.resolve();
67
67
  }
68
68
 
69
- async handleSessionShutdown(): Promise<void> {
69
+ handleSessionShutdown(): Promise<void> {
70
70
  const { session } = this;
71
71
  const ctx = session.getRuntimeContext();
72
72
  if (ctx) {
@@ -74,5 +74,6 @@ export class SessionLifecycleHandler {
74
74
  }
75
75
  session.shutdown();
76
76
  this.cleanupRpc();
77
+ return Promise.resolve();
77
78
  }
78
79
  }
@@ -3,24 +3,24 @@ import type {
3
3
  InputEventResult,
4
4
  } from "@earendil-works/pi-coding-agent";
5
5
 
6
- import { toRecord } from "../common";
6
+ import { toRecord } from "#src/common";
7
7
  import {
8
8
  emitDecisionEvent,
9
9
  type PermissionEventBus,
10
- } from "../permission-events";
11
- import { applyPermissionGate } from "../permission-gate";
12
- import type { PromptPermissionDetails } from "../permission-prompter";
10
+ } from "#src/permission-events";
11
+ import { applyPermissionGate } from "#src/permission-gate";
12
+ import type { PromptPermissionDetails } from "#src/permission-prompter";
13
13
  import {
14
14
  formatMissingToolNameReason,
15
15
  formatSkillAskPrompt,
16
16
  formatUnknownToolReason,
17
- } from "../permission-prompts";
18
- import type { PermissionSession } from "../permission-session";
17
+ } from "#src/permission-prompts";
18
+ import type { PermissionSession } from "#src/permission-session";
19
19
  import {
20
20
  checkRequestedToolRegistration,
21
21
  getToolNameFromValue,
22
22
  type ToolRegistry,
23
- } from "../tool-registry";
23
+ } from "#src/tool-registry";
24
24
  import { describeBashExternalDirectoryGate } from "./gates/bash-external-directory";
25
25
  import { describeBashPathGate } from "./gates/bash-path";
26
26
  import type { GateRunnerDeps } from "./gates/descriptor";
@@ -104,6 +104,7 @@ export class PermissionGateHandler {
104
104
  session.prompt(ctx, details);
105
105
  const emitDecision: GateRunnerDeps["emitDecision"] = (e) =>
106
106
  emitDecisionEvent(this.events, e);
107
+ // eslint-disable-next-line @typescript-eslint/unbound-method -- logger.review is a plain function closure; no this-binding issue
107
108
  const writeReviewLog = session.logger.review;
108
109
  const checkPermission: GateRunnerDeps["checkPermission"] = (
109
110
  surface,
@@ -305,6 +306,7 @@ export class PermissionGateHandler {
305
306
  skillInputAutoApproved = decision.autoApproved === true;
306
307
  return decision;
307
308
  },
309
+ // eslint-disable-next-line @typescript-eslint/unbound-method -- logger.review is a plain function closure; no this-binding issue
308
310
  writeLog: session.logger.review,
309
311
  logContext: {
310
312
  source: "skill_input",
@@ -324,6 +326,7 @@ export class PermissionGateHandler {
324
326
  surface: "skill",
325
327
  value: skillName,
326
328
  result: skillInputGate.action === "allow" ? "allow" : "deny",
329
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition -- defensive fallback; TypeScript narrows check.state before the ternary's else branch */
327
330
  resolution:
328
331
  check.state === "allow"
329
332
  ? "policy_allow"
@@ -336,6 +339,8 @@ export class PermissionGateHandler {
336
339
  : skillInputCanConfirm
337
340
  ? "user_denied"
338
341
  : "confirmation_unavailable",
342
+ /* eslint-enable @typescript-eslint/no-unnecessary-condition */
343
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ?? null normalises undefined to null for the log record
339
344
  origin: check.origin ?? null,
340
345
  agentName: agentName ?? null,
341
346
  matchedPattern: check.matchedPattern ?? null,
package/src/logging.ts CHANGED
@@ -21,12 +21,15 @@ export function safeJsonStringify(value: unknown): string | undefined {
21
21
  }
22
22
 
23
23
  if (typeof currentValue === "object" && currentValue !== null) {
24
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- JSON.stringify replacer receives any; currentValue is narrowed to object here
24
25
  if (seen.has(currentValue)) {
25
26
  return "[Circular]";
26
27
  }
28
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- same as above
27
29
  seen.add(currentValue);
28
30
  }
29
31
 
32
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- JSON.stringify replacer must return any
30
33
  return currentValue;
31
34
  });
32
35
  }
@@ -40,7 +40,7 @@ function discoverGlobalNodeModulesViaSubprocess(): string | null {
40
40
  timeout: 5000,
41
41
  stdio: ["ignore", "pipe", "ignore"],
42
42
  });
43
- const root = result.stdout?.trim();
43
+ const root = result.stdout.trim();
44
44
  if (result.status === 0 && root && existsSync(root)) {
45
45
  return root;
46
46
  }
package/src/normalize.ts CHANGED
@@ -20,6 +20,7 @@ export function normalizeFlatConfig(permission: FlatPermissionConfig): Ruleset {
20
20
  if (isPermissionState(value)) {
21
21
  rules.push({ surface, pattern: "*", action: value, origin: "builtin" });
22
22
  }
23
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive null check; value type does not include null but runtime JSON may
23
24
  } else if (typeof value === "object" && value !== null) {
24
25
  for (const [pattern, action] of Object.entries(value)) {
25
26
  if (isPermissionState(action)) {
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-deprecated -- this module implements the deprecated event-bus RPC channel; references to its own deprecated symbols are intentional */
1
2
  /**
2
3
  * Permission event bus RPC handlers.
3
4
  *
@@ -112,6 +113,7 @@ function handleCheckRpc(
112
113
  const data: PermissionsCheckReplyData = {
113
114
  result: result.state,
114
115
  matchedPattern: result.matchedPattern ?? null,
116
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ?? null normalises undefined to null for the reply record
115
117
  origin: result.origin ?? null,
116
118
  };
117
119
  events.emit(replyChannel, successReply(data));
@@ -78,7 +78,7 @@ export class PermissionManager {
78
78
  }
79
79
 
80
80
  private resolvePermissions(agentName?: string): ResolvedPermissions {
81
- const cacheKey = agentName || "__global__";
81
+ const cacheKey = agentName ?? "__global__";
82
82
  const stamp = this.loader.getCacheStamp(agentName);
83
83
  const cached = this.resolvedPermissionsCache.get(cacheKey);
84
84
  if (cached?.stamp === stamp) {
@@ -107,17 +107,19 @@ export class PermissionManager {
107
107
 
108
108
  for (const [surface, value] of Object.entries(scope.permission)) {
109
109
  const baseVal = mergedPermission[surface];
110
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition -- defensive null/type checks; config values may differ at runtime */
110
111
  const bothObjects =
111
112
  typeof baseVal === "object" &&
112
113
  baseVal !== null &&
113
114
  typeof value === "object" &&
114
115
  value !== null;
116
+ /* eslint-enable @typescript-eslint/no-unnecessary-condition */
115
117
 
116
118
  if (bothObjects) {
117
119
  // Shallow-merge: each incoming pattern is attributed to this scope;
118
120
  // existing patterns from lower scopes keep their earlier origin.
119
121
  if (!origins.has(surface)) origins.set(surface, new Map());
120
- for (const pattern of Object.keys(value as Record<string, unknown>)) {
122
+ for (const pattern of Object.keys(value)) {
121
123
  origins.get(surface)!.set(pattern, scopeName);
122
124
  }
123
125
  } else {
@@ -125,10 +127,9 @@ export class PermissionManager {
125
127
  const surfaceOrigins = new Map<string, RuleOrigin>();
126
128
  if (typeof value === "string") {
127
129
  surfaceOrigins.set("*", scopeName);
130
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive null check
128
131
  } else if (typeof value === "object" && value !== null) {
129
- for (const pattern of Object.keys(
130
- value as Record<string, unknown>,
131
- )) {
132
+ for (const pattern of Object.keys(value)) {
132
133
  surfaceOrigins.set(pattern, scopeName);
133
134
  }
134
135
  }
@@ -146,7 +147,7 @@ export class PermissionManager {
146
147
  // The "*" key feeds synthesizeDefaults() only — it is NOT included as a
147
148
  // config rule so that extension tools fall through to source:"default".
148
149
  const universalFallback = isPermissionState(mergedPermission["*"])
149
- ? (mergedPermission["*"] as PermissionState)
150
+ ? mergedPermission["*"]
150
151
  : DEFAULT_UNIVERSAL_FALLBACK;
151
152
  // Track which scope contributed the universal fallback.
152
153
  const universalFallbackOrigin: RuleOrigin =
@@ -12,15 +12,17 @@ export function mergeFlatPermissions(
12
12
  const merged: FlatPermissionConfig = { ...base };
13
13
  for (const [key, value] of Object.entries(override)) {
14
14
  const baseVal = merged[key];
15
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition -- defensive null/type checks; config values may differ at runtime */
15
16
  if (
16
17
  typeof baseVal === "object" &&
17
18
  baseVal !== null &&
18
19
  typeof value === "object" &&
19
20
  value !== null
20
21
  ) {
22
+ /* eslint-enable @typescript-eslint/no-unnecessary-condition */
21
23
  merged[key] = {
22
- ...(baseVal as Record<string, PermissionState>),
23
- ...(value as Record<string, PermissionState>),
24
+ ...baseVal,
25
+ ...value,
24
26
  };
25
27
  } else {
26
28
  merged[key] = value;
@@ -148,6 +148,7 @@ export class PermissionPrompter implements PermissionPrompterApi {
148
148
  private buildForwardingDeps(): PermissionForwardingDeps {
149
149
  const { deps } = this;
150
150
  const logger: ForwardedPermissionLogger = {
151
+ // eslint-disable-next-line @typescript-eslint/unbound-method -- logger methods are plain function closures; no this-binding issue
151
152
  writeReviewLog: deps.writeReviewLog,
152
153
  writeDebugLog: () => undefined,
153
154
  };
@@ -155,7 +156,9 @@ export class PermissionPrompter implements PermissionPrompterApi {
155
156
  forwardingDir: deps.forwardingDir,
156
157
  subagentSessionsDir: deps.subagentSessionsDir,
157
158
  logger,
159
+ // eslint-disable-next-line @typescript-eslint/unbound-method -- logger methods are plain function closures; no this-binding issue
158
160
  writeReviewLog: deps.writeReviewLog,
161
+ // eslint-disable-next-line @typescript-eslint/unbound-method -- same as above
159
162
  requestPermissionDecisionFromUi: deps.requestPermissionDecisionFromUi,
160
163
  shouldAutoApprove: () => false,
161
164
  };