@diegopetrucci/pi-extensions 0.1.21 → 0.1.24

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/README.md CHANGED
@@ -5,7 +5,8 @@ A collection of [pi](https://github.com/earendil-works/pi-mono) agent extensions
5
5
  - [`confirm-destructive`](./extensions/confirm-destructive): Confirms before destructive session actions like clear, switch, and fork.
6
6
  - [`context-cap`](./extensions/context-cap): Caps effective model context windows at 200k tokens by default so pi avoids the `dumb zone`; toggle temporarily with `/context-cap`.
7
7
  - [`context-inspector`](./extensions/context-inspector): Adds `/context`, a local self-contained HTML dashboard that breaks down where the current session context is going, with category overview, top offenders, and drilldown search.
8
- - [`librarian`](./extensions/librarian): Adds a GitHub research scout that asks whether to use an opt-in local repo checkout cache under the OS user cache directory, with cached repos expiring after 30 days of non-use.
8
+ - [`dirty-repo-guard`](./extensions/dirty-repo-guard): Prompts before new sessions, session switches, or forks when the current git repo has uncommitted changes.
9
+ - [`librarian`](./extensions/librarian): Adds a GitHub research scout with a local repo checkout cache enabled by default under the OS user cache directory, toggleable with `/librarian-cache`, with cached repos expiring after 30 days of non-use.
9
10
  - [`minimal-footer`](./extensions/minimal-footer): Replaces pi's built-in footer with a minimal configurable two-line layout: branch/repo on the first line, context/model on the second, optional `DUMB ZONE`, plus OpenAI Codex 5-hour and 7-day usage when available.
10
11
  - [`notify`](./extensions/notify): Sends configurable terminal, desktop, bell, and sound notifications when pi finishes and is ready for input.
11
12
  - [`openai-fast`](./extensions/openai-fast): Adds `/fast` to enable OpenAI Codex Fast mode for ChatGPT-auth GPT-5.4 and GPT-5.5 by injecting the priority service tier.
@@ -26,7 +27,7 @@ pi install npm:@diegopetrucci/pi-extensions
26
27
  Or pin the GitHub package to this release:
27
28
 
28
29
  ```bash
29
- pi install git:github.com/diegopetrucci/pi-extensions@v0.1.21
30
+ pi install git:github.com/diegopetrucci/pi-extensions@v0.1.24
30
31
  ```
31
32
 
32
33
  Or a specific extension:
@@ -0,0 +1,47 @@
1
+ # dirty-repo-guard
2
+
3
+ A small pi extension that prompts before session changes when the current git repo has uncommitted changes.
4
+
5
+ This is copied from the original `dirty-repo-guard.ts` example in [`earendil-works/pi`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/dirty-repo-guard.ts) and kept basically the same.
6
+
7
+ ## What it checks
8
+
9
+ Before creating a new session, switching sessions, or forking, the extension runs:
10
+
11
+ ```bash
12
+ git status --porcelain
13
+ ```
14
+
15
+ If the command reports changed files, pi asks whether to proceed anyway. If pi is running without an interactive UI, matching actions are cancelled by default.
16
+
17
+ ## Install
18
+
19
+ ### Standalone npm package
20
+
21
+ ```bash
22
+ pi install npm:@diegopetrucci/pi-dirty-repo-guard
23
+ ```
24
+
25
+ ### Collection package
26
+
27
+ ```bash
28
+ pi install npm:@diegopetrucci/pi-extensions
29
+ ```
30
+
31
+ ### GitHub package
32
+
33
+ ```bash
34
+ pi install git:github.com/diegopetrucci/pi-extensions
35
+ ```
36
+
37
+ Then reload pi:
38
+
39
+ ```text
40
+ /reload
41
+ ```
42
+
43
+ ## Notes
44
+
45
+ - Hooks `session_before_switch` and `session_before_fork`.
46
+ - Allows session changes outside git repos.
47
+ - Cancels the action when the user declines.
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Dirty Repo Guard Extension
3
+ *
4
+ * Prevents session changes when there are uncommitted git changes.
5
+ * Useful to ensure work is committed before switching context.
6
+ */
7
+
8
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
9
+
10
+ async function checkDirtyRepo(
11
+ pi: ExtensionAPI,
12
+ ctx: ExtensionContext,
13
+ action: string,
14
+ ): Promise<{ cancel: boolean } | undefined> {
15
+ // Check for uncommitted changes
16
+ const { stdout, code } = await pi.exec("git", ["status", "--porcelain"]);
17
+
18
+ if (code !== 0) {
19
+ // Not a git repo, allow the action
20
+ return;
21
+ }
22
+
23
+ const hasChanges = stdout.trim().length > 0;
24
+ if (!hasChanges) {
25
+ return;
26
+ }
27
+
28
+ if (!ctx.hasUI) {
29
+ // In non-interactive mode, block by default
30
+ return { cancel: true };
31
+ }
32
+
33
+ // Count changed files
34
+ const changedFiles = stdout.trim().split("\n").filter(Boolean).length;
35
+
36
+ const choice = await ctx.ui.select(`You have ${changedFiles} uncommitted file(s). ${action} anyway?`, [
37
+ "Yes, proceed anyway",
38
+ "No, let me commit first",
39
+ ]);
40
+
41
+ if (choice !== "Yes, proceed anyway") {
42
+ ctx.ui.notify("Commit your changes first", "warning");
43
+ return { cancel: true };
44
+ }
45
+ }
46
+
47
+ export default function (pi: ExtensionAPI) {
48
+ pi.on("session_before_switch", async (event, ctx) => {
49
+ const action = event.reason === "new" ? "new session" : "switch session";
50
+ return checkDirtyRepo(pi, ctx, action);
51
+ });
52
+
53
+ pi.on("session_before_fork", async (_event, ctx) => {
54
+ return checkDirtyRepo(pi, ctx, "fork");
55
+ });
56
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@diegopetrucci/pi-dirty-repo-guard",
3
+ "version": "0.1.0",
4
+ "description": "A pi extension that prompts before session changes when the current git repo has uncommitted changes.",
5
+ "keywords": ["pi-package", "pi", "git", "safety"],
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/diegopetrucci/pi-extensions.git",
10
+ "directory": "extensions/dirty-repo-guard"
11
+ },
12
+ "files": [
13
+ "index.ts",
14
+ "README.md"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "pi": {
20
+ "extensions": [
21
+ "index.ts"
22
+ ]
23
+ },
24
+ "peerDependencies": {
25
+ "@earendil-works/pi-coding-agent": "*"
26
+ }
27
+ }
@@ -1,8 +1,8 @@
1
1
  # librarian
2
2
 
3
- A pi GitHub research scout inspired by `pi-librarian`, with an opt-in local checkout cache.
3
+ A pi GitHub research scout inspired by `pi-librarian`, with a local checkout cache enabled by default.
4
4
 
5
- When the `librarian` tool runs, it asks whether to cache/reuse repository checkouts locally. If you say no, cancel, time out, or run without UI, it uses GitHub API/search and temporary fetched files only.
5
+ When the `librarian` tool runs, it can cache/reuse repository checkouts locally. Use `/librarian-cache off` to force GitHub API/search and temporary fetched files only, or `/librarian-cache on` to re-enable cached local checkouts.
6
6
 
7
7
  ## Install
8
8
 
@@ -35,10 +35,21 @@ Then reload pi:
35
35
  - Tool name: `librarian`
36
36
  - Uses a restricted subagent with `bash` and `read`
37
37
  - Uses `gh` for GitHub search/API access
38
- - Asks on each call whether to use cached local checkouts
39
- - Defaults to no checkout/cache
38
+ - Uses cached local checkouts by default
39
+ - Toggle cache behavior for future calls with `/librarian-cache on | off | toggle | status`
40
40
  - Cached repos are removed lazily after 30 days without use
41
41
 
42
+ ## Commands
43
+
44
+ ```text
45
+ /librarian-cache status
46
+ /librarian-cache off
47
+ /librarian-cache on
48
+ /librarian-cache toggle
49
+ ```
50
+
51
+ The command works in interactive mode, RPC mode, and print/JSON mode. It writes a global preference to `~/.pi/agent/extensions/librarian.json`, so separate non-UI invocations use the same setting. In non-UI modes, command feedback is written to stderr so stdout remains usable for normal output or JSON events.
52
+
42
53
  ## Cache location
43
54
 
44
55
  macOS:
@@ -25,11 +25,14 @@ const CACHE_TTL_DAYS = 30;
25
25
  const CACHE_TTL_MS = CACHE_TTL_DAYS * 24 * 60 * 60 * 1000;
26
26
  const CACHE_METADATA_FILE = ".pi-librarian-cache.json";
27
27
  const CACHE_MARKER_FILE = ".pi-librarian-cache-used";
28
+ const CACHE_CONFIG_FILE = "librarian.json";
28
29
 
29
30
  type LibrarianStatus = "running" | "done" | "error" | "aborted";
30
31
 
31
32
  type CacheMode = "disabled" | "enabled";
32
33
 
34
+ const DEFAULT_CACHE_MODE: CacheMode = "enabled";
35
+
33
36
  type ToolCall = {
34
37
  id: string;
35
38
  name: string;
@@ -252,30 +255,72 @@ async function cleanupExpiredCache(cacheRoot: string): Promise<{ deleted: number
252
255
  }
253
256
  }
254
257
 
255
- async function askForCache(ctx: ExtensionContext, cacheRoot: string): Promise<{ enabled: boolean; reason: string }> {
256
- if (!ctx.hasUI) return { enabled: false, reason: "no UI available; using GitHub API/temp files only" };
258
+ function getCacheConfigPath(): string {
259
+ return path.join(getAgentDir(), "extensions", CACHE_CONFIG_FILE);
260
+ }
261
+
262
+ function parseCacheMode(value: unknown): CacheMode | undefined {
263
+ if (typeof value === "string") {
264
+ const normalized = value.trim().toLowerCase();
265
+ if (normalized === "enabled" || normalized === "on" || normalized === "true") return "enabled";
266
+ if (normalized === "disabled" || normalized === "off" || normalized === "false") return "disabled";
267
+ }
268
+ if (value === true) return "enabled";
269
+ if (value === false) return "disabled";
270
+ return undefined;
271
+ }
257
272
 
273
+ async function readCachePreference(): Promise<CacheMode> {
258
274
  try {
259
- const enabled = await ctx.ui.confirm(
260
- "Librarian repo cache",
261
- [
262
- "Cache/reuse local GitHub repo checkouts for this Librarian call?",
263
- "",
264
- `Cache directory: ${cacheRoot}`,
265
- `Repos unused for ${CACHE_TTL_DAYS} days are removed lazily on future Librarian calls.`,
266
- "",
267
- "Choose No to use GitHub API and temporary fetched files only.",
268
- ].join("\n"),
269
- { timeout: 30_000 },
275
+ const raw = await fs.readFile(getCacheConfigPath(), "utf8");
276
+ const parsed = JSON.parse(raw) as {
277
+ cacheMode?: unknown;
278
+ cacheEnabled?: unknown;
279
+ cache?: { mode?: unknown; enabled?: unknown };
280
+ };
281
+ return (
282
+ parseCacheMode(parsed.cacheMode) ??
283
+ parseCacheMode(parsed.cache?.mode) ??
284
+ parseCacheMode(parsed.cacheEnabled) ??
285
+ parseCacheMode(parsed.cache?.enabled) ??
286
+ DEFAULT_CACHE_MODE
270
287
  );
271
-
272
- return enabled
273
- ? { enabled: true, reason: "user opted into cached local checkouts" }
274
- : { enabled: false, reason: "user declined or prompt timed out" };
275
288
  } catch (error) {
276
- const message = error instanceof Error ? error.message : String(error);
277
- return { enabled: false, reason: `cache prompt failed (${message}); using GitHub API/temp files only` };
289
+ return (error as NodeJS.ErrnoException).code === "ENOENT" ? DEFAULT_CACHE_MODE : "disabled";
290
+ }
291
+ }
292
+
293
+ async function writeCachePreference(preference: CacheMode): Promise<void> {
294
+ const configPath = getCacheConfigPath();
295
+ const config = {
296
+ cacheMode: preference,
297
+ cacheEnabled: preference === "enabled",
298
+ updatedAt: new Date().toISOString(),
299
+ };
300
+
301
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
302
+ await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
303
+ }
304
+
305
+ function resolveCacheDecision(preference: CacheMode): { enabled: boolean; reason: string } {
306
+ if (preference === "enabled") {
307
+ return { enabled: true, reason: "cache preference enabled; using cached local checkouts" };
308
+ }
309
+
310
+ return { enabled: false, reason: "cache preference disabled; using GitHub API/temp files only" };
311
+ }
312
+
313
+ function formatCachePreference(preference: CacheMode): string {
314
+ return preference === "enabled" ? "on" : "off";
315
+ }
316
+
317
+ function notifyCommand(ctx: ExtensionContext, message: string, type: "info" | "warning" | "error" = "info"): void {
318
+ if (ctx.hasUI) {
319
+ ctx.ui.notify(message, type);
320
+ return;
278
321
  }
322
+
323
+ console.error(message);
279
324
  }
280
325
 
281
326
  function resolveToolPath(cwd: string, rawPath: string): string {
@@ -284,7 +329,12 @@ function resolveToolPath(cwd: string, rawPath: string): string {
284
329
  }
285
330
 
286
331
  function getBlockedBashReason(command: string, options: { workspace: string; cacheRoot: string; cacheEnabled: boolean }): string | undefined {
287
- const scan = command.split(options.workspace).join("<WORKSPACE>").split(options.cacheRoot).join("<CACHE>");
332
+ if (!options.cacheEnabled && command.includes(options.cacheRoot)) {
333
+ return "Local repo checkout cache is disabled for this Librarian call.";
334
+ }
335
+
336
+ let scan = command.split(options.workspace).join("<WORKSPACE>");
337
+ if (options.cacheEnabled) scan = scan.split(options.cacheRoot).join("<CACHE>");
288
338
  if (/(^|[\n;&|()])\s*\//.test(scan)) return "Librarian bash blocks absolute-path executables.";
289
339
 
290
340
  const destructiveLocal = /(^|[\n;&|()])\s*(?:sudo|su|rm|rmdir|mv|cp|chmod|chown|dd|truncate|killall|pkill|launchctl|osascript|pbcopy|pbpaste|eval|exec|xargs)(?=$|[\s;&|()])/;
@@ -438,13 +488,81 @@ function isAbortLikeError(error: unknown): boolean {
438
488
  }
439
489
 
440
490
  export default function librarianExtension(pi: ExtensionAPI) {
491
+ let cachePreference: CacheMode = DEFAULT_CACHE_MODE;
492
+
493
+ pi.on("session_start", async () => {
494
+ cachePreference = await readCachePreference();
495
+ });
496
+
497
+ pi.registerCommand("librarian-cache", {
498
+ description: "Toggle Librarian local checkout cache for future librarian calls",
499
+ getArgumentCompletions: (prefix) => {
500
+ const commands = ["on", "off", "toggle", "status"];
501
+ const query = prefix.trim().toLowerCase();
502
+ const matches = commands.filter((command) => command.startsWith(query));
503
+ return matches.length > 0 ? matches.map((value) => ({ value, label: value })) : null;
504
+ },
505
+ handler: async (args, ctx) => {
506
+ const action = args.trim().toLowerCase() || "status";
507
+ const cacheRoot = getUserCacheReposRoot();
508
+ const configPath = getCacheConfigPath();
509
+
510
+ const setPreference = async (mode: CacheMode): Promise<string | undefined> => {
511
+ cachePreference = mode;
512
+ try {
513
+ await writeCachePreference(mode);
514
+ return undefined;
515
+ } catch (error) {
516
+ const message = error instanceof Error ? error.message : String(error);
517
+ return `Preference changed for this process, but could not save ${configPath}: ${message}`;
518
+ }
519
+ };
520
+
521
+ const formatSetMessage = (mode: CacheMode, warning?: string): string => {
522
+ const main = mode === "enabled"
523
+ ? `Librarian cache enabled. Future librarian calls will reuse local checkouts under ${cacheRoot}.`
524
+ : "Librarian cache disabled. Future librarian calls will use GitHub API/search and temporary fetched files only.";
525
+ return warning ? `${main} ${warning}` : main;
526
+ };
527
+
528
+ if (action === "on" || action === "enable") {
529
+ const warning = await setPreference("enabled");
530
+ notifyCommand(ctx, formatSetMessage("enabled", warning), warning ? "warning" : "info");
531
+ return;
532
+ }
533
+
534
+ if (action === "off" || action === "disable") {
535
+ const warning = await setPreference("disabled");
536
+ notifyCommand(ctx, formatSetMessage("disabled", warning), warning ? "warning" : "info");
537
+ return;
538
+ }
539
+
540
+ if (action === "toggle") {
541
+ const next = cachePreference === "enabled" ? "disabled" : "enabled";
542
+ const warning = await setPreference(next);
543
+ notifyCommand(ctx, formatSetMessage(next, warning), warning ? "warning" : "info");
544
+ return;
545
+ }
546
+
547
+ if (action === "status") {
548
+ notifyCommand(
549
+ ctx,
550
+ `Librarian cache is ${formatCachePreference(cachePreference)}. Cache directory: ${cacheRoot}. Config: ${configPath}. Repos unused for ${CACHE_TTL_DAYS} days are removed lazily.`,
551
+ );
552
+ return;
553
+ }
554
+
555
+ notifyCommand(ctx, "Usage: /librarian-cache on | off | toggle | status", "warning");
556
+ },
557
+ });
558
+
441
559
  pi.registerTool({
442
560
  name: "librarian",
443
561
  label: "Librarian",
444
562
  description:
445
- "GitHub research scout for coding and personal-assistant tasks. Use when the answer likely lives in GitHub repos, exact repo/path locations are unknown, or you'd otherwise do exploratory gh search/tree probes plus local rg/read inspection. Librarian asks whether to use an optional 30-day local checkout cache, otherwise it behaves like API-only pi-librarian.",
563
+ "GitHub research scout for coding and personal-assistant tasks. Use when the answer likely lives in GitHub repos, exact repo/path locations are unknown, or you'd otherwise do exploratory gh search/tree probes plus local rg/read inspection. Librarian uses an optional 30-day local checkout cache by default; toggle it with /librarian-cache.",
446
564
  promptSnippet:
447
- "Research GitHub repositories with evidence-first path and line citations; optionally ask the user to cache local repo checkouts.",
565
+ "Research GitHub repositories with evidence-first path and line citations; local checkout cache is enabled by default and user-toggleable with /librarian-cache.",
448
566
  promptGuidelines: [
449
567
  "Use librarian when the answer likely requires exploratory GitHub repository search or line-cited evidence from external repos.",
450
568
  "Do not use librarian for files already present in the current workspace unless the user asks for external GitHub research.",
@@ -472,9 +590,21 @@ export default function librarianExtension(pi: ExtensionAPI) {
472
590
  await fs.mkdir(path.join(workspace, "repos"), { recursive: true });
473
591
 
474
592
  const cacheRoot = getUserCacheReposRoot();
475
- const cacheDecision = await askForCache(ctx, cacheRoot);
476
- if (cacheDecision.enabled) await fs.mkdir(cacheRoot, { recursive: true });
477
- const cleanup = cacheDecision.enabled ? await cleanupExpiredCache(cacheRoot) : { deleted: 0, errors: [] };
593
+ let cacheDecision = resolveCacheDecision(cachePreference);
594
+ let cleanup: { deleted: number; errors: string[] } = { deleted: 0, errors: [] };
595
+ if (cacheDecision.enabled) {
596
+ try {
597
+ await fs.mkdir(cacheRoot, { recursive: true });
598
+ cleanup = await cleanupExpiredCache(cacheRoot);
599
+ } catch (error) {
600
+ const message = error instanceof Error ? error.message : String(error);
601
+ cacheDecision = {
602
+ enabled: false,
603
+ reason: `cache setup failed (${message}); using GitHub API/temp files only`,
604
+ };
605
+ cleanup = { deleted: 0, errors: [`cache setup: ${message}`] };
606
+ }
607
+ }
478
608
 
479
609
  const details: LibrarianDetails = {
480
610
  status: "running",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-librarian",
3
- "version": "0.1.0",
4
- "description": "A pi GitHub research scout that can optionally cache local repo checkouts under the user's OS cache directory.",
3
+ "version": "0.1.1",
4
+ "description": "A pi GitHub research scout with a toggleable local repo checkout cache under the user's OS cache directory.",
5
5
  "keywords": ["pi-package", "pi", "github", "research", "subagent", "cache"],
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -25,6 +25,7 @@ Fast mode is only injected when all of these are true:
25
25
  ## Commands
26
26
 
27
27
  ```text
28
+ /fast
28
29
  /fast status
29
30
  /fast on
30
31
  /fast off
@@ -32,7 +33,7 @@ Fast mode is only injected when all of these are true:
32
33
  /fast toggle
33
34
  ```
34
35
 
35
- `/fast on` and `/fast off` are temporary session/runtime overrides. Use `/fast auto` to reload and follow config defaults again.
36
+ Run `/fast` without arguments to pick an action from a menu. `/fast on` and `/fast off` are temporary session/runtime overrides. Use `/fast auto` to reload and follow config defaults again.
36
37
 
37
38
  The extension defaults to off so installing the full collection does not accidentally spend Fast-mode credits.
38
39
 
@@ -249,7 +249,17 @@ export default function openAIFastExtension(pi: ExtensionAPI) {
249
249
  },
250
250
  handler: async (args, ctx) => {
251
251
  const state = getState(ctx);
252
- const action = args.trim().toLowerCase() || "status";
252
+ let action = args.trim().toLowerCase();
253
+
254
+ if (!action) {
255
+ if (!ctx.hasUI) {
256
+ action = "status";
257
+ } else {
258
+ const selection = await ctx.ui.select("OpenAI Fast mode", COMMANDS);
259
+ if (!selection) return;
260
+ action = selection;
261
+ }
262
+ }
253
263
 
254
264
  if (action === "on" || action === "enable") {
255
265
  state.override = "on";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-openai-fast",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A pi extension that enables OpenAI Codex Fast mode for ChatGPT-auth GPT-5.4 and GPT-5.5 by injecting the priority service tier.",
5
5
  "keywords": ["pi-package", "pi", "openai", "codex", "fast"],
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-extensions",
3
- "version": "0.1.21",
4
- "description": "A collection of pi extensions, including a GitHub librarian with opt-in local repo checkout caching, a minimal custom footer, an Amp-style oracle, a 200k context cap for auto-compaction, a local HTML context inspector, OpenAI Codex Fast mode controls, quiet one-line collapsed invocation previews, a permission gate for dangerous bash commands, confirm-before-destructive session actions, and terminal notifications when pi is ready for input.",
3
+ "version": "0.1.24",
4
+ "description": "A collection of pi extensions, including a GitHub librarian with toggleable local repo checkout caching, a minimal custom footer, an Amp-style oracle, a 200k context cap for auto-compaction, a local HTML context inspector, a dirty repository guard for session changes, OpenAI Codex Fast mode controls, quiet one-line collapsed invocation previews, a permission gate for dangerous bash commands, confirm-before-destructive session actions, and terminal notifications when pi is ready for input.",
5
5
  "keywords": ["pi-package", "pi", "terminal", "agent"],
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -32,6 +32,7 @@
32
32
  "./extensions/oracle/index.ts",
33
33
  "./extensions/context-cap/index.ts",
34
34
  "./extensions/context-inspector/index.ts",
35
+ "./extensions/dirty-repo-guard/index.ts",
35
36
  "./extensions/librarian/index.ts",
36
37
  "./extensions/quiet-tools/index.ts",
37
38
  "./extensions/permission-gate/index.ts",