@gotgenes/pi-permission-system 10.1.0 → 10.3.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.
@@ -1,4 +1,3 @@
1
- import { join } from "node:path";
2
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
2
 
4
3
  // ── logger stub ────────────────────────────────────────────────────────────
@@ -6,9 +5,6 @@ const {
6
5
  mockLoggerDebug,
7
6
  mockLoggerReview,
8
7
  mockCreateLogger,
9
- mockLoadAndMergeConfigs,
10
- mockSyncPermissionSystemStatus,
11
- mockBuildResolvedConfigLogEntry,
12
8
  mockDiscoverGlobalNodeModulesRoot,
13
9
  } = vi.hoisted(() => ({
14
10
  mockLoggerDebug:
@@ -20,9 +16,6 @@ const {
20
16
  (event: string, details?: Record<string, unknown>) => string | undefined
21
17
  >(),
22
18
  mockCreateLogger: vi.fn(),
23
- mockLoadAndMergeConfigs: vi.fn(),
24
- mockSyncPermissionSystemStatus: vi.fn(),
25
- mockBuildResolvedConfigLogEntry: vi.fn(),
26
19
  mockDiscoverGlobalNodeModulesRoot: vi.fn<() => string | null>(),
27
20
  }));
28
21
 
@@ -34,21 +27,6 @@ vi.mock("../src/permission-manager", () => ({
34
27
  PermissionManager: vi.fn(),
35
28
  }));
36
29
 
37
- vi.mock("../src/config-loader", () => ({
38
- loadAndMergeConfigs: mockLoadAndMergeConfigs,
39
- loadUnifiedConfig: vi.fn().mockReturnValue({ config: {} }),
40
- }));
41
-
42
- vi.mock("../src/status", () => ({
43
- PERMISSION_SYSTEM_STATUS_KEY: "permission-system",
44
- syncPermissionSystemStatus: mockSyncPermissionSystemStatus,
45
- getPermissionSystemStatus: vi.fn(),
46
- }));
47
-
48
- vi.mock("../src/config-reporter", () => ({
49
- buildResolvedConfigLogEntry: mockBuildResolvedConfigLogEntry,
50
- }));
51
-
52
30
  vi.mock("../src/subagent-context", () => ({
53
31
  isSubagentExecutionContext: vi.fn().mockReturnValue(false),
54
32
  }));
@@ -62,20 +40,9 @@ vi.mock("../src/session-rules", () => ({
62
40
  deriveApprovalPattern: vi.fn(),
63
41
  }));
64
42
 
65
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
66
- import {
67
- getGlobalConfigPath,
68
- getGlobalLogsDir,
69
- getProjectConfigPath,
70
- } from "#src/config-paths";
43
+ import { getGlobalLogsDir } from "#src/config-paths";
71
44
  import { DEFAULT_EXTENSION_CONFIG } from "#src/extension-config";
72
- import { PermissionManager } from "#src/permission-manager";
73
- import {
74
- createExtensionRuntime,
75
- createPermissionManagerForCwd,
76
- derivePiProjectPaths,
77
- refreshExtensionConfig,
78
- } from "#src/runtime";
45
+ import { createExtensionRuntime } from "#src/runtime";
79
46
 
80
47
  // ── test suite ─────────────────────────────────────────────────────────────
81
48
 
@@ -162,9 +129,9 @@ describe("createExtensionRuntime", () => {
162
129
 
163
130
  // ── Default mutable state ────────────────────────────────────────────────
164
131
 
165
- it("initializes config to DEFAULT_EXTENSION_CONFIG", () => {
132
+ it("initializes configStore.current() to DEFAULT_EXTENSION_CONFIG", () => {
166
133
  const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
167
- expect(runtime.config).toEqual(DEFAULT_EXTENSION_CONFIG);
134
+ expect(runtime.configStore.current()).toEqual(DEFAULT_EXTENSION_CONFIG);
168
135
  });
169
136
 
170
137
  it("initializes runtimeContext to null", () => {
@@ -192,11 +159,6 @@ describe("createExtensionRuntime", () => {
192
159
  expect(runtime.lastPromptStateCacheKey).toBeNull();
193
160
  });
194
161
 
195
- it("initializes lastConfigWarning to null", () => {
196
- const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
197
- expect(runtime.lastConfigWarning).toBeNull();
198
- });
199
-
200
162
  it("creates a sessionRules instance", () => {
201
163
  const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
202
164
  expect(runtime.sessionRules).toBeDefined();
@@ -204,17 +166,6 @@ describe("createExtensionRuntime", () => {
204
166
 
205
167
  // ── Mutable state is writable ──────────────────────────────────────────
206
168
 
207
- it("allows config to be updated", () => {
208
- const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
209
- const newConfig = {
210
- debugLog: true,
211
- permissionReviewLog: false,
212
- yoloMode: false,
213
- };
214
- runtime.config = newConfig;
215
- expect(runtime.config).toEqual(newConfig);
216
- });
217
-
218
169
  it("allows runtimeContext to be updated", () => {
219
170
  const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
220
171
  const mockCtx = { hasUI: false } as never;
@@ -237,19 +188,13 @@ describe("createExtensionRuntime", () => {
237
188
  expect(opts.reviewLogPath).toContain(expectedLogsDir);
238
189
  });
239
190
 
240
- it("passes getConfig that reads current runtime.config", () => {
241
- const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
191
+ it("passes getConfig that reads from configStore.current()", () => {
192
+ createExtensionRuntime({ agentDir: "/test/agent" });
242
193
  const opts = mockCreateLogger.mock.calls[0][0] as {
243
194
  getConfig: () => typeof DEFAULT_EXTENSION_CONFIG;
244
195
  };
245
- const updatedConfig = {
246
- debugLog: true,
247
- permissionReviewLog: false,
248
- yoloMode: false,
249
- };
250
- runtime.config = updatedConfig;
251
- // getConfig() should reflect the updated value
252
- expect(opts.getConfig()).toEqual(updatedConfig);
196
+ // getConfig() reads from configStore.current() — DEFAULT_EXTENSION_CONFIG at startup
197
+ expect(opts.getConfig()).toEqual(DEFAULT_EXTENSION_CONFIG);
253
198
  });
254
199
 
255
200
  // ── writeDebugLog delegates to logger.debug ──────────────────────────────
@@ -351,217 +296,8 @@ describe("createExtensionRuntime", () => {
351
296
  });
352
297
  });
353
298
 
354
- // ── derivePiProjectPaths ───────────────────────────────────────────────────
355
-
356
- describe("derivePiProjectPaths", () => {
357
- it("returns null for null cwd", () => {
358
- expect(derivePiProjectPaths(null)).toBeNull();
359
- });
360
-
361
- it("returns null for undefined cwd", () => {
362
- expect(derivePiProjectPaths(undefined)).toBeNull();
363
- });
364
-
365
- it("returns null for empty string cwd", () => {
366
- expect(derivePiProjectPaths("")).toBeNull();
367
- });
368
-
369
- it("returns projectGlobalConfigPath via getProjectConfigPath", () => {
370
- const result = derivePiProjectPaths("/my/project");
371
- expect(result?.projectGlobalConfigPath).toBe(
372
- getProjectConfigPath("/my/project"),
373
- );
374
- });
375
-
376
- it("returns projectAgentsDir as .pi/agent/agents under cwd", () => {
377
- const result = derivePiProjectPaths("/my/project");
378
- expect(result?.projectAgentsDir).toBe(
379
- join("/my/project", ".pi", "agent", "agents"),
380
- );
381
- });
382
- });
383
-
384
- // ── createPermissionManagerForCwd ─────────────────────────────────────────
385
-
386
- describe("createPermissionManagerForCwd", () => {
387
- beforeEach(() => {
388
- // PermissionManager is already mocked as vi.fn() at module scope.
389
- });
390
-
391
- it("creates a PermissionManager with globalConfigPath from agentDir", () => {
392
- const MockPM = PermissionManager as ReturnType<typeof vi.fn>;
393
- MockPM.mockClear();
394
- createPermissionManagerForCwd("/test/agent", null);
395
- expect(MockPM).toHaveBeenCalledWith(
396
- expect.objectContaining({
397
- globalConfigPath: getGlobalConfigPath("/test/agent"),
398
- }),
399
- );
400
- });
401
-
402
- it("includes projectGlobalConfigPath when cwd is provided", () => {
403
- const MockPM = PermissionManager as ReturnType<typeof vi.fn>;
404
- MockPM.mockClear();
405
- createPermissionManagerForCwd("/test/agent", "/my/project");
406
- expect(MockPM).toHaveBeenCalledWith(
407
- expect.objectContaining({
408
- globalConfigPath: getGlobalConfigPath("/test/agent"),
409
- projectGlobalConfigPath: getProjectConfigPath("/my/project"),
410
- }),
411
- );
412
- });
413
-
414
- it("excludes projectGlobalConfigPath when cwd is null", () => {
415
- const MockPM = PermissionManager as ReturnType<typeof vi.fn>;
416
- MockPM.mockClear();
417
- createPermissionManagerForCwd("/test/agent", null);
418
- const callArg = MockPM.mock.calls[0][0] as Record<string, unknown>;
419
- expect(callArg.projectGlobalConfigPath).toBeUndefined();
420
- });
421
- });
422
-
423
- // ── refreshExtensionConfig ────────────────────────────────────────────────
424
-
425
- describe("refreshExtensionConfig", () => {
426
- function makeRuntime() {
427
- mockCreateLogger.mockReturnValue({
428
- debug: mockLoggerDebug,
429
- review: mockLoggerReview,
430
- });
431
- return createExtensionRuntime({ agentDir: "/test/agent" });
432
- }
433
-
434
- function makeCtx(
435
- overrides: Partial<ExtensionContext> = {},
436
- ): ExtensionContext {
437
- return {
438
- cwd: "/test/project",
439
- hasUI: false,
440
- ui: { notify: vi.fn(), setStatus: vi.fn() },
441
- sessionManager: { getEntries: vi.fn(), addEntry: vi.fn() },
442
- ...overrides,
443
- } as unknown as ExtensionContext;
444
- }
445
-
446
- beforeEach(() => {
447
- mockLoggerDebug.mockReset().mockReturnValue(undefined);
448
- mockLoggerReview.mockReset().mockReturnValue(undefined);
449
- mockLoadAndMergeConfigs.mockReset().mockReturnValue({
450
- merged: { ...DEFAULT_EXTENSION_CONFIG },
451
- issues: [],
452
- });
453
- mockSyncPermissionSystemStatus.mockReset();
454
- });
455
-
456
- it("updates runtime.runtimeContext when ctx is provided", () => {
457
- const runtime = makeRuntime();
458
- const ctx = makeCtx();
459
- refreshExtensionConfig(runtime, ctx);
460
- expect(runtime.runtimeContext).toBe(ctx);
461
- });
462
-
463
- it("does not override runtimeContext when ctx is omitted", () => {
464
- const runtime = makeRuntime();
465
- const existing = makeCtx();
466
- runtime.runtimeContext = existing;
467
- refreshExtensionConfig(runtime);
468
- expect(runtime.runtimeContext).toBe(existing);
469
- });
470
-
471
- it("updates runtime.config with normalized merged result", () => {
472
- const runtime = makeRuntime();
473
- mockLoadAndMergeConfigs.mockReturnValue({
474
- merged: { debugLog: true, permissionReviewLog: false, yoloMode: false },
475
- issues: [],
476
- });
477
- refreshExtensionConfig(runtime);
478
- expect(runtime.config.debugLog).toBe(true);
479
- expect(runtime.config.permissionReviewLog).toBe(false);
480
- });
481
-
482
- it("calls loadAndMergeConfigs with runtime.agentDir and cwd", () => {
483
- const runtime = makeRuntime();
484
- const ctx = makeCtx({ cwd: "/my/project" });
485
- refreshExtensionConfig(runtime, ctx);
486
- expect(mockLoadAndMergeConfigs).toHaveBeenCalledWith(
487
- "/test/agent",
488
- "/my/project",
489
- expect.any(String), // EXTENSION_ROOT
490
- );
491
- });
492
-
493
- it("writes config.loaded debug log", () => {
494
- const runtime = makeRuntime();
495
- refreshExtensionConfig(runtime);
496
- expect(mockLoggerDebug).toHaveBeenCalledWith(
497
- "config.loaded",
498
- expect.objectContaining({ debugLog: false }),
499
- );
500
- });
501
-
502
- it("sets lastConfigWarning when issues are present", () => {
503
- const runtime = makeRuntime();
504
- mockLoadAndMergeConfigs.mockReturnValue({
505
- merged: { ...DEFAULT_EXTENSION_CONFIG },
506
- issues: ["legacy config detected"],
507
- });
508
- refreshExtensionConfig(runtime);
509
- expect(runtime.lastConfigWarning).toBe("legacy config detected");
510
- });
511
-
512
- it("clears lastConfigWarning when no issues", () => {
513
- const runtime = makeRuntime();
514
- runtime.lastConfigWarning = "old warning";
515
- mockLoadAndMergeConfigs.mockReturnValue({
516
- merged: { ...DEFAULT_EXTENSION_CONFIG },
517
- issues: [],
518
- });
519
- refreshExtensionConfig(runtime);
520
- expect(runtime.lastConfigWarning).toBeNull();
521
- });
522
-
523
- it("notifies UI when a new warning appears and hasUI is true", () => {
524
- const runtime = makeRuntime();
525
- const mockNotify = vi.fn();
526
- const ctx = makeCtx({ hasUI: true, ui: { notify: mockNotify } as never });
527
- mockLoadAndMergeConfigs.mockReturnValue({
528
- merged: { ...DEFAULT_EXTENSION_CONFIG },
529
- issues: ["new warning"],
530
- });
531
- refreshExtensionConfig(runtime, ctx);
532
- expect(mockNotify).toHaveBeenCalledWith("new warning", "warning");
533
- });
534
-
535
- it("does not re-notify the same warning on subsequent calls", () => {
536
- const runtime = makeRuntime();
537
- const mockNotify = vi.fn();
538
- const ctx = makeCtx({ hasUI: true, ui: { notify: mockNotify } as never });
539
- mockLoadAndMergeConfigs.mockReturnValue({
540
- merged: { ...DEFAULT_EXTENSION_CONFIG },
541
- issues: ["persistent warning"],
542
- });
543
- refreshExtensionConfig(runtime, ctx);
544
- refreshExtensionConfig(runtime, ctx);
545
- expect(mockNotify).toHaveBeenCalledTimes(1);
546
- });
547
-
548
- it("calls syncPermissionSystemStatus when hasUI is true", () => {
549
- const runtime = makeRuntime();
550
- const ctx = makeCtx({ hasUI: true });
551
- refreshExtensionConfig(runtime, ctx);
552
- expect(mockSyncPermissionSystemStatus).toHaveBeenCalledWith(
553
- ctx,
554
- expect.any(Object),
555
- );
556
- });
557
-
558
- it("does not call syncPermissionSystemStatus when hasUI is false", () => {
559
- const runtime = makeRuntime();
560
- const ctx = makeCtx({ hasUI: false });
561
- refreshExtensionConfig(runtime, ctx);
562
- expect(mockSyncPermissionSystemStatus).not.toHaveBeenCalled();
563
- });
564
- });
299
+ // refreshExtensionConfig / saveExtensionConfig / logResolvedConfigPaths are
300
+ // thin delegators to runtime.configStore — behavior covered in config-store.test.ts.
565
301
 
566
302
  // resolveAgentName was moved to PermissionSession (#129)
567
- // Tests live in tests/permission-session.test.ts
303
+ // Tests live in test/permission-session.test.ts