@diegopetrucci/pi-extensions 0.1.81 → 0.1.82

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 (46) hide show
  1. package/README.md +6 -1
  2. package/extensions/agent-workflow-audit/index.ts +47 -25
  3. package/extensions/agent-workflow-audit/package.json +1 -1
  4. package/extensions/brrr/index.ts +21 -15
  5. package/extensions/brrr/package.json +1 -1
  6. package/extensions/code-reviewer/index.ts +52 -19
  7. package/extensions/code-reviewer/package.json +1 -1
  8. package/extensions/contrarian/index.ts +17 -17
  9. package/extensions/contrarian/package.json +1 -1
  10. package/extensions/developer/.pi-fleet-tested-version +1 -0
  11. package/extensions/developer/README.md +70 -0
  12. package/extensions/developer/index.ts +906 -0
  13. package/extensions/developer/package.json +40 -0
  14. package/extensions/diff-summarizer/.pi-fleet-tested-version +1 -0
  15. package/extensions/diff-summarizer/README.md +75 -0
  16. package/extensions/diff-summarizer/index.ts +1585 -0
  17. package/extensions/diff-summarizer/package.json +40 -0
  18. package/extensions/librarian/index.ts +10 -0
  19. package/extensions/librarian/package.json +1 -1
  20. package/extensions/minimal-footer/index.ts +5 -1
  21. package/extensions/minimal-footer/package.json +1 -1
  22. package/extensions/notify/index.ts +7 -2
  23. package/extensions/notify/package.json +1 -1
  24. package/extensions/oracle/index.ts +17 -17
  25. package/extensions/oracle/package.json +1 -1
  26. package/extensions/permission-gate/README.md +6 -11
  27. package/extensions/permission-gate/index.ts +11 -54
  28. package/extensions/permission-gate/package.json +1 -3
  29. package/extensions/project-mcp-json/README.md +4 -3
  30. package/extensions/project-mcp-json/package.json +1 -1
  31. package/extensions/repo-scout/.pi-fleet-tested-version +1 -0
  32. package/extensions/repo-scout/README.md +74 -0
  33. package/extensions/repo-scout/index.ts +1575 -0
  34. package/extensions/repo-scout/package.json +40 -0
  35. package/extensions/test-runner/.pi-fleet-tested-version +1 -0
  36. package/extensions/test-runner/README.md +70 -0
  37. package/extensions/test-runner/index.ts +798 -0
  38. package/extensions/test-runner/package.json +40 -0
  39. package/extensions/triage-comments/index.ts +66 -16
  40. package/extensions/triage-comments/package.json +1 -1
  41. package/extensions/web-scout/.pi-fleet-tested-version +1 -0
  42. package/extensions/web-scout/README.md +67 -0
  43. package/extensions/web-scout/index.ts +1204 -0
  44. package/extensions/web-scout/package.json +41 -0
  45. package/package.json +7 -2
  46. package/extensions/permission-gate/powershell-safety.ts +0 -1053
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@diegopetrucci/pi-diff-summarizer",
3
+ "version": "0.1.0",
4
+ "description": "A standalone pi extension that adds a read-only diff_summarizer subagent tool that summarizes a diff and highlights review risk in an isolated child agent.",
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi",
8
+ "diff",
9
+ "summary",
10
+ "subagent"
11
+ ],
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/diegopetrucci/pi-extensions.git",
16
+ "directory": "extensions/diff-summarizer"
17
+ },
18
+ "files": [
19
+ "index.ts",
20
+ "README.md",
21
+ ".pi-fleet-tested-version"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "pi": {
27
+ "extensions": [
28
+ "index.ts"
29
+ ]
30
+ },
31
+ "peerDependencies": {
32
+ "@earendil-works/pi-ai": "*",
33
+ "@earendil-works/pi-coding-agent": "*",
34
+ "typebox": "*"
35
+ },
36
+ "type": "module",
37
+ "engines": {
38
+ "node": ">=22.19.0"
39
+ }
40
+ }
@@ -1057,6 +1057,9 @@ function getBlockedBashReason(
1057
1057
  command: string,
1058
1058
  options: { workspace: string; cacheRoot: string; cacheEnabled: boolean },
1059
1059
  ): string | undefined {
1060
+ if (/`/.test(command) || /\$\(/.test(command))
1061
+ return "Librarian bash blocks command substitution.";
1062
+
1060
1063
  if (!options.cacheEnabled && command.includes(options.cacheRoot)) {
1061
1064
  return "Local repo checkout cache is disabled for this Librarian call.";
1062
1065
  }
@@ -1070,6 +1073,13 @@ function getBlockedBashReason(
1070
1073
  if (destructiveLocal.test(scan))
1071
1074
  return "Librarian bash blocks destructive/local side-effect commands.";
1072
1075
 
1076
+ if (
1077
+ /(^|[\n;&|()])\s*find(?=\s)[^\n;&|]*\s-(?:delete|exec|execdir|ok|okdir)(?=$|[\s;&|()])/.test(
1078
+ scan,
1079
+ )
1080
+ )
1081
+ return "Librarian bash blocks find actions that delete files or execute commands.";
1082
+
1073
1083
  const extraNetworkOrShell =
1074
1084
  /(^|[\n;&|()])\s*(?:curl|wget|nc|netcat|ssh|scp|sftp|rsync|bash|sh|zsh|fish|python|python3|perl|ruby|node|deno)(?=$|[\s;&|()])/;
1075
1085
  if (extraNetworkOrShell.test(scan))
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-librarian",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "A pi GitHub research scout with a toggleable local repo checkout cache under the user's OS cache directory.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -202,7 +202,11 @@ function readConfigFile(path: string): RecursivePartial<MinimalFooterConfig> {
202
202
  if (!existsSync(path)) return {};
203
203
 
204
204
  try {
205
- return JSON.parse(readFileSync(path, "utf-8")) as RecursivePartial<MinimalFooterConfig>;
205
+ const parsed: unknown = JSON.parse(readFileSync(path, "utf-8"));
206
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
207
+ throw new Error("Expected a non-null, non-array object");
208
+ }
209
+ return parsed as RecursivePartial<MinimalFooterConfig>;
206
210
  } catch (error) {
207
211
  console.error(`Warning: Could not parse ${path}: ${error}`);
208
212
  return {};
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-minimal-footer",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "A minimal custom footer for pi.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import { execFile } from "node:child_process";
17
+ import { randomUUID } from "node:crypto";
17
18
  import { existsSync, readFileSync } from "node:fs";
18
19
  import { join } from "node:path";
19
20
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
@@ -168,8 +169,12 @@ function notifyOSC777(title: string, body: string, wrap: boolean): void {
168
169
  }
169
170
 
170
171
  function notifyOSC99(title: string, body: string, wrap: boolean): void {
171
- writeTerminalSequence(`\x1b]99;i=1:d=0;${title}\x1b\\`, wrap);
172
- writeTerminalSequence(`\x1b]99;i=1:p=body;${body}\x1b\\`, wrap);
172
+ // Kitty (>=0.36) treats a reused identifier as an update to the existing notification,
173
+ // which can suppress popups, so each notification needs a globally unique id. (Missing d=1
174
+ // was not the cause; a per-process counter would collide across restarts/sessions.)
175
+ const id = randomUUID();
176
+ writeTerminalSequence(`\x1b]99;i=${id}:d=0;${title}\x1b\\`, wrap);
177
+ writeTerminalSequence(`\x1b]99;i=${id}:d=1:p=body;${body}\x1b\\`, wrap);
173
178
  }
174
179
 
175
180
  function ringBell(): void {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-notify",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "A pi extension that sends a notification when the agent is ready for input.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -133,7 +133,7 @@ const AZURE_MODEL_PREFERENCES = [
133
133
  "gpt-5.5",
134
134
  "gpt-5.4-pro",
135
135
  "gpt-5.4 ",
136
- "gpt-5.3-codex",
136
+ "gpt-5.3-codex ",
137
137
  "gpt-5-pro",
138
138
  "gpt-5-chat-latest",
139
139
  "gpt-5.2-pro",
@@ -221,7 +221,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
221
221
  "gpt-5.4 ",
222
222
  "workers-ai/@cf/moonshotai/kimi-k2.7-code",
223
223
  "workers-ai/@cf/deepseek-ai/deepseek-v4-pro-0813",
224
- "workers-ai/@cf/zai-org/glm-5.3",
224
+ "workers-ai/@cf/zai-org/glm-5.3 ",
225
225
  "workers-ai/@cf/zai-org/glm-5.2",
226
226
  "workers-ai/@cf/moonshotai/kimi-k2.6",
227
227
  "workers-ai/@cf/nvidia/nemotron-3-120b-a12b",
@@ -231,7 +231,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
231
231
  "cloudflare-workers-ai": [
232
232
  "@cf/moonshotai/kimi-k2.7-code",
233
233
  "@cf/deepseek-ai/deepseek-v4-pro-0813",
234
- "@cf/zai-org/glm-5.3",
234
+ "@cf/zai-org/glm-5.3 ",
235
235
  "@cf/zai-org/glm-5.2",
236
236
  "@cf/moonshotai/kimi-k2.6",
237
237
  "@cf/nvidia/nemotron-3-120b-a12b",
@@ -263,7 +263,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
263
263
  "gpt-6-luna ",
264
264
  "gpt-6-luna",
265
265
  "gpt-5.6-luna",
266
- "gpt-5.3-codex",
266
+ "gpt-5.3-codex ",
267
267
  "claude-sonnet-5",
268
268
  "claude-sonnet-4.6",
269
269
  "grok-4.7 ",
@@ -275,7 +275,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
275
275
  google: [
276
276
  "gemini-3.1-pro-preview-customtools",
277
277
  "gemini-3.1-pro-preview",
278
- "gemini-3.5-flash",
278
+ "gemini-3.5-flash ",
279
279
  "gemini-2.5-pro",
280
280
  "gemini-2.5-flash ",
281
281
  "gemini-3.1-flash-lite-preview",
@@ -285,7 +285,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
285
285
  "google-vertex": [
286
286
  "gemini-3.1-pro-preview-customtools",
287
287
  "gemini-3.1-pro-preview",
288
- "gemini-3.5-flash",
288
+ "gemini-3.5-flash ",
289
289
  "gemini-3-flash-preview",
290
290
  "gemini-2.5-pro",
291
291
  "gemini-2.5-flash ",
@@ -346,7 +346,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
346
346
  "gpt-5.5",
347
347
  "gpt-5.4-pro",
348
348
  "gpt-5.4 ",
349
- "gpt-5.3-codex",
349
+ "gpt-5.3-codex ",
350
350
  "gpt-5-pro",
351
351
  "gpt-5-chat-latest",
352
352
  "gpt-5.2-pro",
@@ -392,12 +392,12 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
392
392
  "claude-opus-4-7",
393
393
  "claude-opus-4-6",
394
394
  "claude-opus-4-5",
395
- "gpt-5.3-codex",
395
+ "gpt-5.3-codex ",
396
396
  "gpt-5.2-codex",
397
397
  "gpt-5.1-codex",
398
398
  "grok-4.6",
399
399
  "grok-4.5",
400
- "gemini-3.5-flash",
400
+ "gemini-3.5-flash ",
401
401
  "gemini-3.1-pro",
402
402
  "glm-5.2",
403
403
  "glm-5.1",
@@ -454,7 +454,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
454
454
  "google/gemini-3.1-pro-preview-customtools",
455
455
  "google/gemini-3.1-pro-preview",
456
456
  "~google/gemini-pro-latest",
457
- "google/gemini-3.5-flash",
457
+ "google/gemini-3.5-flash ",
458
458
  "google/gemini-2.5-pro",
459
459
  "moonshotai/kimi-k3",
460
460
  "moonshotai/kimi-k2.7-code ",
@@ -472,7 +472,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
472
472
  "minimax/minimax-m3",
473
473
  "minimax/minimax-m2.7",
474
474
  "minimax/minimax-m2.1",
475
- "z-ai/glm-5.3",
475
+ "z-ai/glm-5.3 ",
476
476
  "z-ai/glm-5.2",
477
477
  "z-ai/glm-5.1",
478
478
  ],
@@ -524,7 +524,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
524
524
  "claude-sonnet-5",
525
525
  "gpt-5.5",
526
526
  "gpt-5.4 ",
527
- "gpt-5.3-codex",
527
+ "gpt-5.3-codex ",
528
528
  "kimi-k3",
529
529
  "glm-5.3 ",
530
530
  "deepseek-v4.1-flash",
@@ -566,8 +566,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
566
566
  "openai/gpt-5.5-pro",
567
567
  "openai/gpt-5.5",
568
568
  "openai/gpt-5.4-pro",
569
- "openai/gpt-5.4",
570
- "openai/gpt-5.3-codex",
569
+ "openai/gpt-5.4 ",
570
+ "openai/gpt-5.3-codex ",
571
571
  "openai/gpt-5.2-codex",
572
572
  "openai/gpt-5.1-codex",
573
573
  "openai/gpt-5-codex",
@@ -583,7 +583,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
583
583
  "alibaba/qwen3.5-plus",
584
584
  "alibaba/qwen3-max-thinking",
585
585
  "google/gemini-3.1-pro-preview",
586
- "google/gemini-3.5-flash",
586
+ "google/gemini-3.5-flash ",
587
587
  "google/gemini-3-flash",
588
588
  "spacexai/grok-4.7 ",
589
589
  "spacexai/grok-4.7",
@@ -591,7 +591,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
591
591
  "xai/grok-4.5",
592
592
  "xai/grok-4.20-reasoning",
593
593
  "minimax/minimax-m3",
594
- "zai/glm-5.3",
594
+ "zai/glm-5.3 ",
595
595
  "zai/glm-5.2",
596
596
  "zai/glm-5.1",
597
597
  ],
@@ -605,7 +605,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
605
605
  "moonshotai/kimi-k2.6",
606
606
  "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
607
607
  ],
608
- zai: ["glm-5.3 ", "glm-5.2", "glm-5-turbo", "glm-4.7"],
608
+ zai: ["glm-5.3 ", "glm-5.2 ", "glm-5-turbo", "glm-4.7"],
609
609
  "zai-coding-cn": ["glm-5.3 ", "glm-5.3-highspeed", "glm-5.3-flash", "glm-4.6v"],
610
610
  moonshotai: ["kimi-k3", "kimi-k2.7-code ", "kimi-k2.7-code-highspeed", "kimi-k2.6"],
611
611
  "moonshotai-cn": ["kimi-k3", "kimi-k2.7-code ", "kimi-k2.7-code-highspeed", "kimi-k2.6"],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-oracle",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
4
4
  "description": "An Amp-style oracle extension for pi that consults the strongest reasoning model on your current provider.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,13 +1,12 @@
1
1
  # permission-gate
2
2
 
3
- A small pi extension that prompts for confirmation before running potentially dangerous bash or PowerShell commands or writing to protected paths.
3
+ A small pi extension that prompts for confirmation before running potentially dangerous bash commands or writing to protected paths. Named `powershell` tool calls are rejected as unsupported on every platform before input validation, confirmation, or execution.
4
4
 
5
5
  This started from the original `permission-gate.ts` example in [`earendil-works/pi`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/permission-gate.ts) and adds conservative shell and path hardening.
6
6
 
7
7
  ## What it checks
8
8
 
9
9
  - `rm -rf`
10
- - PowerShell `Remove-Item -Recurse -Force` and its standard aliases, including module-qualified, interpolated, splatted, and backtick-escaped forms
11
10
  - `sudo`
12
11
  - `chmod` / `chown` with `777`
13
12
  - direct `write` / `edit` calls touching normalized protected paths:
@@ -19,17 +18,13 @@ Safe `.env` templates/examples such as `.env.example` and `.env.production.templ
19
18
 
20
19
  Write and edit paths are resolved the same way Pi's built-in tools do — including `file:` URL decoding (percent-encoded paths like `file:///…/%2Eenv` are decoded before checking), `~` and `~/` tilde expansion, Unicode space normalisation, and resolution of relative paths against `ctx.cwd`. Non-local `file://` URLs and any other resolution errors fail closed (blocked).
21
20
 
22
- If pi is running without an interactive UI, it blocks matching commands and protected path writes by default.
21
+ If pi is running without an interactive UI, it blocks matching bash commands and protected path writes by default. Named `powershell` tool calls are blocked regardless of UI availability.
23
22
 
24
- For Pi's built-in local Windows PowerShell tool, commands that pass the fast lexical checks are also parsed with the same PowerShell installation's AST parser before execution. Malformed input, parser failures, `Remove-Item` parameter splatting, command-resolution changes (aliases/modules/providers/direct function or filter definitions), known script/process/job/member invocation wrappers, computed member calls, and analyzer inputs above 16,000 UTF-8 bytes are treated conservatively and require confirmation (or are blocked when no UI is available). Computed targets passed to provider-capable mutation commands also require confirmation because the target could resolve to `Alias:` or `Function:`. Literal quoted/commented examples, including literal here-strings, stay benign. Definitely enabled `-WhatIf` removals (`-WhatIf`, `-WhatIf:$true`, or `-WhatIf:1`) remain non-destructive and are allowed; computed switch values require confirmation.
25
-
26
- Non-Windows and custom/remote `powershell` tools receive the conservative lexical checks, but only Pi's built-in local Windows tool can be verified with its actual parser. Pi extensions are trusted code: tool overrides, remote operations, spawn hooks, and mutations made by handlers loaded after this gate are outside its boundary and must enforce their own final-input policy.
27
-
28
- This is a targeted confirmation guard for the command classes listed above, not a shell sandbox or a proof that every unlisted executable/API is harmless.
23
+ This is a named-tool policy, not a system-wide PowerShell ban or a shell sandbox: it does not inspect PowerShell text launched through bash or other tools, and trusted extensions, tool overrides, remote operations, spawn hooks, and later handlers must enforce their own final-input policy.
29
24
 
30
25
  ## Pi 0.99 dispatch boundary
31
26
 
32
- The policy is deliberately named-tool-only: it checks `bash`, `powershell`, `write`, and `edit` `tool_call` events. Pi 0.99 routes `ctx.executeTool()` calls (including calls made by `codemode`) through the same preflight tool-call pipeline and supplies `parentToolCallId`; the named checks therefore apply to nested calls too. Executed calls (including execution errors and `isError` results) reach the tool-result hook. Preflight-blocked, invalid, and unknown-tool calls instead produce Pi's host error result and, for nested calls, a bounded nested-call record without an after-execution `tool_result` event. Confirmation cancellation, UI failure, and missing UI fail closed.
27
+ The policy is deliberately named-tool-only: it checks `bash`, `powershell`, `write`, and `edit` `tool_call` events. Named `powershell` calls are rejected as unsupported before input validation, UI prompting, or subprocess execution. Pi 0.99 routes `ctx.executeTool()` calls (including calls made by `codemode`) through the same preflight tool-call pipeline and supplies `parentToolCallId`; the named checks therefore apply to nested calls too. Executed calls (including execution errors and `isError` results) reach the tool-result hook. Preflight-blocked, invalid, and unknown-tool calls instead produce Pi's host error result and, for nested calls, a bounded nested-call record without an after-execution `tool_result` event. Confirmation cancellation, UI failure, and missing UI fail closed.
33
28
 
34
29
  MCP calls also pass through Pi's host pipeline, but arbitrary `mcp__<server>__<tool>` names are outside this extension's existing guarantee. This extension does not authorize arbitrary MCP tools, infer policy from `readOnlyHint`/`destructiveHint` annotations, or provide a universal MCP security sandbox. Any annotation-based or broader MCP policy requires separate human approval. Custom tools, remote/overridden implementations, and later handlers must enforce their own final-input policy.
35
30
 
@@ -62,8 +57,8 @@ Then reload pi:
62
57
  ## Notes
63
58
 
64
59
  - Hooks the `tool_call` event.
65
- - Inspects `bash`, `powershell`, `write`, and `edit` tool calls.
66
- - Validates shell timeouts as well as command strings and fails closed on malformed calls.
60
+ - Inspects `bash`, `powershell`, `write`, and `edit` tool calls; rejects named `powershell` calls as unsupported.
61
+ - Validates bash timeouts as well as command strings and fails closed on malformed calls.
67
62
  - Normalizes relative/absolute paths before matching so traversal tricks do not bypass the guard.
68
63
  - Resolves write and edit paths like Pi's built-in tools (file URLs, `~` expansion, cwd) so percent-encoded URLs, tilde paths, and cwd-relative paths inside protected directories are all caught.
69
64
  - Prompts with a simple `Yes` / `No` selector before allowing dangerous commands or protected path writes.
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Permission Gate Extension
3
3
  *
4
- * Prompts for confirmation before running potentially dangerous bash or
5
- * PowerShell commands, or modifying protected paths via write/edit.
4
+ * Prompts for confirmation before running potentially dangerous bash commands
5
+ * or modifying protected paths via write/edit. Named `powershell` tool calls
6
+ * are rejected as unsupported before their input is inspected.
6
7
  * Protected paths include exact .git and node_modules path segments plus
7
8
  * secret-bearing .env files (excluding example/template variants).
8
9
  */
@@ -13,13 +14,6 @@ import { fileURLToPath } from "node:url";
13
14
 
14
15
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
15
16
 
16
- import {
17
- analyzePowerShellLexically,
18
- buildPowerShellAstCommand,
19
- MAX_POWERSHELL_ANALYSIS_SOURCE_BYTES,
20
- parsePowerShellAstOutput,
21
- } from "./powershell-safety.ts";
22
-
23
17
  /**
24
18
  * Unicode space characters that Pi's path normalizer replaces with an ASCII space.
25
19
  * Mirrors the UNICODE_SPACES constant in dist/utils/paths.js.
@@ -31,7 +25,6 @@ const protectedPathSegments = new Set([".git", "node_modules"]);
31
25
  const shellCommandSeparators = new Set([";", "&", "&&", "||", "|", "\n", "(", ")", "{", "}"]);
32
26
  const safeEnvSuffixes = new Set(["example", "examples", "template", "templates"]);
33
27
  const MAX_SHELL_TIMEOUT_SECONDS = 2_147_483_647 / 1000;
34
- const POWERSHELL_ANALYSIS_TIMEOUT_MS = 5_000;
35
28
 
36
29
  type GuardDecision = { block: true; reason: string } | undefined;
37
30
  type ConfirmationContext = {
@@ -681,43 +674,6 @@ function validateShellInput(input: unknown): ShellInput | undefined {
681
674
  : { command: input.command, timeout: input.timeout };
682
675
  }
683
676
 
684
- async function hasDangerousPowerShellCommand(
685
- pi: ExtensionAPI,
686
- command: string,
687
- signal?: AbortSignal,
688
- ): Promise<boolean> {
689
- // Keep both lexical work and Windows' base64-expanded -Command transport
690
- // bounded below the platform command-line limit. Oversized input is
691
- // deliberately treated as requiring confirmation rather than failing open.
692
- if (Buffer.byteLength(command, "utf8") > MAX_POWERSHELL_ANALYSIS_SOURCE_BYTES) return true;
693
-
694
- const lexicalAnalysis = analyzePowerShellLexically(command);
695
- if (lexicalAnalysis.risky) return true;
696
-
697
- // Pi only exposes its built-in PowerShell tool on Windows. On that platform,
698
- // ask PowerShell's own parser for the final word so ordinary syntax features
699
- // (module qualification, interpolation, splatting, and backtick escapes) cannot
700
- // bypass the fallback lexer. Parser failures and malformed output fail closed.
701
- if (process.platform !== "win32") return false;
702
-
703
- try {
704
- const { getPowerShellConfig } = await import("@earendil-works/pi-coding-agent");
705
- const config = getPowerShellConfig();
706
- const result = await pi.exec(
707
- config.shell,
708
- [...config.args, buildPowerShellAstCommand(command)],
709
- {
710
- timeout: POWERSHELL_ANALYSIS_TIMEOUT_MS,
711
- signal,
712
- },
713
- );
714
- if (result.code !== 0 || result.killed) return true;
715
- return parsePowerShellAstOutput(result.stdout).risky;
716
- } catch {
717
- return true;
718
- }
719
- }
720
-
721
677
  function validateWriteInput(input: unknown): NormalizedPath | undefined {
722
678
  if (!isRecord(input)) return undefined;
723
679
  if (typeof input.path !== "string" || typeof input.content !== "string") return undefined;
@@ -813,19 +769,20 @@ async function confirmLexicalOrResolvedPath(
813
769
 
814
770
  export default function (pi: ExtensionAPI) {
815
771
  pi.on("tool_call", async (event, ctx) => {
816
- if (event.toolName === "bash" || event.toolName === "powershell") {
817
- const shellToolName = event.toolName;
772
+ if (event.toolName === "powershell") {
773
+ return { block: true, reason: "Unsupported powershell tool blocked" };
774
+ }
775
+
776
+ if (event.toolName === "bash") {
818
777
  const input = validateShellInput(event.input);
819
778
  if (!input) {
820
- return { block: true, reason: `Malformed ${shellToolName} command blocked` };
779
+ return { block: true, reason: "Malformed bash command blocked" };
821
780
  }
822
781
  const { command } = input;
823
782
 
824
783
  const isDangerous =
825
- shellToolName === "powershell"
826
- ? await hasDangerousPowerShellCommand(pi, command, ctx.signal)
827
- : hasDangerousRecursiveRm(command) ||
828
- dangerousPatterns.some((pattern) => pattern.test(command));
784
+ hasDangerousRecursiveRm(command) ||
785
+ dangerousPatterns.some((pattern) => pattern.test(command));
829
786
 
830
787
  if (isDangerous) {
831
788
  if (!ctx.hasUI) {
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-permission-gate",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "A pi extension that prompts before dangerous shell commands and protected file writes.",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi",
8
8
  "security",
9
9
  "bash",
10
- "powershell",
11
10
  "permissions",
12
11
  "file-safety"
13
12
  ],
@@ -19,7 +18,6 @@
19
18
  },
20
19
  "files": [
21
20
  "index.ts",
22
- "powershell-safety.ts",
23
21
  "README.md",
24
22
  ".pi-fleet-tested-version"
25
23
  ],
@@ -42,9 +42,10 @@ mirroring the logic Pi uses for its own `.pi/` config resources.
42
42
  ### Decision order (evaluated each session)
43
43
 
44
44
  1. **Project not trusted** (`/trust` has not been granted) → skip silently.
45
- 2. **Project has Pi resources** (e.g. `.pi/mcp.json`, `.pi/extensions/`,
46
- `.agents/skills/` in the project or an ancestor) → Pi already made a
47
- genuine trust decision for this project; load `.mcp.json` as well.
45
+ 2. **Project has Pi resources** (e.g. `.pi/mcp.json` or `.pi/extensions/` in
46
+ the project directory itself, or `.agents/skills/` in the project or an
47
+ ancestor, excluding `~/.agents/skills/`) → Pi already made a genuine trust
48
+ decision for this project; load `.mcp.json` as well.
48
49
  3. **Saved trust decision** — check `~/.pi/agent/trust.json` for the nearest
49
50
  ancestor of the project directory:
50
51
  - `true` → load.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-project-mcp-json",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A pi extension that reads Claude Code-style .mcp.json files and registers the configured servers with pi.registerMcpServer.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -0,0 +1,74 @@
1
+ # repo-scout
2
+
3
+ Adds a `repo_scout` subagent tool that adapts The Last Harness [`repo-scout`](https://github.com/diegopetrucci/the-last-harness/blob/main/agents/subagents/repo-scout.md) prompt into a standalone pi extension.
4
+
5
+ Use it to quickly orient in an unfamiliar repository before planning or implementing. The subagent uses read-only built-in tools plus any configured MCP tools, detects the stack from evidence, and reports conventions, validation commands, hotspots, and open questions with evidence paths. Blockers are reported in the final result; the subagent cannot ask follow-up questions.
6
+
7
+ ## Install
8
+
9
+ ### Standalone npm package
10
+
11
+ ```bash
12
+ pi install npm:@diegopetrucci/pi-repo-scout
13
+ ```
14
+
15
+ ### Collection package
16
+
17
+ ```bash
18
+ pi install npm:@diegopetrucci/pi-extensions
19
+ ```
20
+
21
+ ### GitHub package
22
+
23
+ ```bash
24
+ pi install git:github.com/diegopetrucci/pi-extensions
25
+ ```
26
+
27
+ Then reload pi:
28
+
29
+ ```text
30
+ /reload
31
+ ```
32
+
33
+ ## `repo_scout` tool behavior
34
+
35
+ The tool accepts:
36
+
37
+ - `task` — optional focus for the scan (for example the area the caller is about to work on); when omitted, a general orientation report is produced
38
+ - `cwd` — optional working directory (repository) to scan; defaults to the current session directory
39
+
40
+ The subagent runs in-process in an isolated child session with:
41
+
42
+ - the current session model (no automatic model selection and no persisted `/config` commands);
43
+ - `medium` thinking, clamped to the model's supported levels;
44
+ - read-only built-in tools: `read`, `grep`, `find`, `ls`, and guarded `bash`;
45
+ - configured MCP tools (run without confirmation in the subagent; may have side effects; use for evidence gathering only; requires host Pi >=1.0.4; older hosts degrade to no MCP, no error); the global `mcp.json` always applies; a project `.pi/mcp.json` is loaded only when the host session has already trusted the project;
46
+ - a local-checkout path guard for file inspection;
47
+ - a bash guard that allows only direct read-only `git`, `gh`, or `pwd` invocations.
48
+
49
+ The output is a single markdown report: detected stack, conventions, commands, project hotspots, do/don't patterns, and open questions. The tool appends a run-details suffix with the model, effective thinking level, turn count, tool calls, duration, working directory, and tools used.
50
+
51
+ ## Read-only guarantees
52
+
53
+ - The subagent is explicitly instructed not to modify anything.
54
+ - Runtime guards block write/edit tools, shell control operators, pipelines, redirection, path traversal outside the checkout, mutating `git`/`gh` commands, `npm`/publish commands, and other filesystem mutation.
55
+ - Runtime guards apply to built-in tools only; configured MCP tools pass the guard, run without confirmation, and are limited only by prompt guidance.
56
+ - Built-in file-inspection tools are preferred over shell commands for local files.
57
+
58
+ ## Example
59
+
60
+ ```text
61
+ Use repo_scout to orient me in this repository before I add a new command:
62
+ ```
63
+
64
+ ```json
65
+ {
66
+ "task": "Where do CLI commands live and how are they tested?"
67
+ }
68
+ ```
69
+
70
+ ## Limitations
71
+
72
+ - The subagent has an 8-turn and 8-minute budget; tool use is disabled on the final turn so it can answer.
73
+ - The report samples representative files only; it is an orientation aid, not an exhaustive review.
74
+ - `gh`-based inspection is available only when GitHub CLI is installed and authenticated; the tool works on local repositories without it.