@diegopetrucci/pi-extensions 0.1.80 → 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.
- package/README.md +6 -1
- package/extensions/agent-workflow-audit/index.ts +47 -25
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/brrr/index.ts +21 -15
- package/extensions/brrr/package.json +1 -1
- package/extensions/code-reviewer/index.ts +52 -19
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/contrarian/index.ts +17 -17
- package/extensions/contrarian/package.json +1 -1
- package/extensions/developer/.pi-fleet-tested-version +1 -0
- package/extensions/developer/README.md +70 -0
- package/extensions/developer/index.ts +906 -0
- package/extensions/developer/package.json +40 -0
- package/extensions/diff-summarizer/.pi-fleet-tested-version +1 -0
- package/extensions/diff-summarizer/README.md +75 -0
- package/extensions/diff-summarizer/index.ts +1585 -0
- package/extensions/diff-summarizer/package.json +40 -0
- package/extensions/dirty-repo-guard/index.ts +5 -2
- package/extensions/dirty-repo-guard/package.json +1 -1
- package/extensions/fast/README.md +1 -5
- package/extensions/fast/index.ts +13 -76
- package/extensions/fast/package.json +1 -1
- package/extensions/inline-bash/index.ts +1 -0
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/index.ts +10 -0
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/index.ts +5 -1
- package/extensions/minimal-footer/package.json +1 -1
- package/extensions/notify/index.ts +7 -2
- package/extensions/notify/package.json +1 -1
- package/extensions/oracle/index.ts +17 -17
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/README.md +6 -11
- package/extensions/permission-gate/index.ts +11 -54
- package/extensions/permission-gate/package.json +1 -3
- package/extensions/project-mcp-json/README.md +4 -3
- package/extensions/project-mcp-json/package.json +1 -1
- package/extensions/repo-scout/.pi-fleet-tested-version +1 -0
- package/extensions/repo-scout/README.md +74 -0
- package/extensions/repo-scout/index.ts +1575 -0
- package/extensions/repo-scout/package.json +40 -0
- package/extensions/test-runner/.pi-fleet-tested-version +1 -0
- package/extensions/test-runner/README.md +70 -0
- package/extensions/test-runner/index.ts +798 -0
- package/extensions/test-runner/package.json +40 -0
- package/extensions/triage-comments/index.ts +66 -16
- package/extensions/triage-comments/package.json +1 -1
- package/extensions/web-scout/.pi-fleet-tested-version +1 -0
- package/extensions/web-scout/README.md +67 -0
- package/extensions/web-scout/index.ts +1204 -0
- package/extensions/web-scout/package.json +41 -0
- package/package.json +7 -2
- package/extensions/permission-gate/powershell-safety.ts +0 -1053
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@diegopetrucci/pi-test-runner",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A standalone pi extension that adds a test_runner subagent tool that runs exact validation steps in an isolated child agent.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pi",
|
|
8
|
+
"testing",
|
|
9
|
+
"validation",
|
|
10
|
+
"subagent"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/diegopetrucci/pi-extensions.git",
|
|
16
|
+
"directory": "extensions/test-runner"
|
|
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
|
+
}
|
|
@@ -175,11 +175,39 @@ const SAFE_GIT_BRANCH_FLAGS = new Set([
|
|
|
175
175
|
"--remotes",
|
|
176
176
|
"-v",
|
|
177
177
|
"-vv",
|
|
178
|
+
"--verbose",
|
|
179
|
+
"-l",
|
|
180
|
+
"--list",
|
|
178
181
|
"--show-current",
|
|
182
|
+
"--column",
|
|
183
|
+
"--no-column",
|
|
184
|
+
"--color",
|
|
185
|
+
"--no-color",
|
|
186
|
+
"-i",
|
|
187
|
+
"--ignore-case",
|
|
188
|
+
]);
|
|
189
|
+
const SAFE_GIT_BRANCH_VALUE_FLAGS = new Set([
|
|
190
|
+
"--contains",
|
|
191
|
+
"--no-contains",
|
|
192
|
+
"--merged",
|
|
193
|
+
"--no-merged",
|
|
194
|
+
"--points-at",
|
|
195
|
+
"--sort",
|
|
196
|
+
"--format",
|
|
197
|
+
]);
|
|
198
|
+
// Flags that make `git branch` list instead of create a branch from positional args.
|
|
199
|
+
const GIT_BRANCH_LISTING_FLAGS = new Set([
|
|
200
|
+
"-a",
|
|
201
|
+
"--all",
|
|
202
|
+
"-r",
|
|
203
|
+
"--remotes",
|
|
204
|
+
"-l",
|
|
179
205
|
"--list",
|
|
180
206
|
"--contains",
|
|
207
|
+
"--no-contains",
|
|
181
208
|
"--merged",
|
|
182
209
|
"--no-merged",
|
|
210
|
+
"--points-at",
|
|
183
211
|
]);
|
|
184
212
|
const SAFE_GIT_GLOBAL_FLAGS = new Set(["--no-pager", "--no-optional-locks"]);
|
|
185
213
|
const SAFE_GIT_CONFIG_OVERRIDES = [
|
|
@@ -1015,22 +1043,27 @@ function getGitSubcommand(tokens: string[]): {
|
|
|
1015
1043
|
function isSafeGitBranchCommand(tokens: string[], subcommandIndex: number): boolean {
|
|
1016
1044
|
const args = tokens.slice(subcommandIndex + 1);
|
|
1017
1045
|
if (args.length === 0) return true;
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1046
|
+
let hasListingMode = false;
|
|
1047
|
+
let hasPositional = false;
|
|
1048
|
+
for (const arg of args) {
|
|
1049
|
+
if (!arg.startsWith("-")) {
|
|
1050
|
+
hasPositional = true;
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
const name = arg.split("=", 1)[0] ?? "";
|
|
1054
|
+
const hasValue = arg.includes("=");
|
|
1055
|
+
if (!hasValue && SAFE_GIT_BRANCH_FLAGS.has(arg)) {
|
|
1056
|
+
if (GIT_BRANCH_LISTING_FLAGS.has(arg)) hasListingMode = true;
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
if (SAFE_GIT_BRANCH_VALUE_FLAGS.has(name)) {
|
|
1060
|
+
if (GIT_BRANCH_LISTING_FLAGS.has(name)) hasListingMode = true;
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1025
1063
|
return false;
|
|
1026
1064
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
SAFE_GIT_BRANCH_FLAGS.has(arg) ||
|
|
1030
|
-
arg.startsWith("--contains=") ||
|
|
1031
|
-
arg.startsWith("--merged=") ||
|
|
1032
|
-
arg.startsWith("--no-merged="),
|
|
1033
|
-
);
|
|
1065
|
+
if (args.includes("--show-current")) return !hasPositional;
|
|
1066
|
+
return !hasPositional || hasListingMode;
|
|
1034
1067
|
}
|
|
1035
1068
|
|
|
1036
1069
|
function isSafeGitRemoteCommand(tokens: string[], subcommandIndex: number): boolean {
|
|
@@ -1038,7 +1071,7 @@ function isSafeGitRemoteCommand(tokens: string[], subcommandIndex: number): bool
|
|
|
1038
1071
|
if (args.length === 0) return true;
|
|
1039
1072
|
if (args.length === 1 && (args[0] === "-v" || args[0] === "--verbose")) return true;
|
|
1040
1073
|
const remoteAction = args.find((arg) => !arg.startsWith("-"));
|
|
1041
|
-
return remoteAction === "
|
|
1074
|
+
return remoteAction === "get-url";
|
|
1042
1075
|
}
|
|
1043
1076
|
|
|
1044
1077
|
function isBlockedGitLocalFileOption(
|
|
@@ -1143,7 +1176,7 @@ function getBlockedGitReason(command: string): string | undefined {
|
|
|
1143
1176
|
}
|
|
1144
1177
|
if (parsed.subcommand === "remote") {
|
|
1145
1178
|
if (!isSafeGitRemoteCommand(tokens, parsed.index)) {
|
|
1146
|
-
return "Triage bash allows git remote only for read-only list
|
|
1179
|
+
return "Triage bash allows git remote only for read-only list or get-url queries.";
|
|
1147
1180
|
}
|
|
1148
1181
|
return undefined;
|
|
1149
1182
|
}
|
|
@@ -1193,6 +1226,9 @@ function getBlockedGhApiReason(tokens: string[]): string | undefined {
|
|
|
1193
1226
|
) {
|
|
1194
1227
|
return "Triage bash allows read-only gh api calls only; request fields and input files are blocked.";
|
|
1195
1228
|
}
|
|
1229
|
+
if (token === "--cache" || lowerToken.startsWith("--cache=")) {
|
|
1230
|
+
return "Triage bash blocks gh api --cache because it writes local cache files.";
|
|
1231
|
+
}
|
|
1196
1232
|
|
|
1197
1233
|
let method: string | undefined;
|
|
1198
1234
|
if (lowerToken === "-x" || lowerToken === "--method") {
|
|
@@ -1238,6 +1274,20 @@ function getBlockedGhReason(command: string): string | undefined {
|
|
|
1238
1274
|
if (/\bgh\s+auth\s+(?:login|logout|refresh|setup-git|token)\b/i.test(command)) {
|
|
1239
1275
|
return "Triage bash blocks gh auth commands and token inspection.";
|
|
1240
1276
|
}
|
|
1277
|
+
if (
|
|
1278
|
+
tokens.some((token) => {
|
|
1279
|
+
const lowerToken = token.toLowerCase();
|
|
1280
|
+
return (
|
|
1281
|
+
lowerToken === "--web" ||
|
|
1282
|
+
lowerToken === "-w" ||
|
|
1283
|
+
(/^-[a-z]{2,}$/i.test(token) && lowerToken.includes("w")) ||
|
|
1284
|
+
lowerToken.startsWith("--web=") ||
|
|
1285
|
+
lowerToken.startsWith("-w=")
|
|
1286
|
+
);
|
|
1287
|
+
})
|
|
1288
|
+
) {
|
|
1289
|
+
return "Triage bash blocks gh --web because it can launch external browser helpers.";
|
|
1290
|
+
}
|
|
1241
1291
|
const apiReason = getBlockedGhApiReason(tokens);
|
|
1242
1292
|
if (apiReason) return apiReason;
|
|
1243
1293
|
if (
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.84.4
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# web-scout
|
|
2
|
+
|
|
3
|
+
Adds a `web_scout` subagent tool that adapts The Last Harness [`web-scout`](https://github.com/diegopetrucci/the-last-harness/blob/main/agents/subagents/web-scout.md) prompt into a standalone pi extension.
|
|
4
|
+
|
|
5
|
+
Use it for focused, read-only web research that needs cited sources. The subagent searches and fetches pages through the hosted [Exa](https://exa.ai) MCP endpoint and reports findings with URLs, retrieval timestamps, and short verbatim quotes. 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-web-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
|
+
## `web_scout` tool behavior
|
|
34
|
+
|
|
35
|
+
The tool accepts:
|
|
36
|
+
|
|
37
|
+
- `task` — required research question
|
|
38
|
+
- `cwd` — optional working directory used for local read-only context; 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
|
+
- `web_search` and `fetch_url`, implemented in this package by calling `https://mcp.exa.ai/mcp` with plain JSON-RPC over HTTP (`web_search_exa` / `web_fetch_exa`); no SDK dependency;
|
|
45
|
+
- read-only built-in tools: `read`, `grep`, `find`, `ls`;
|
|
46
|
+
- configured MCP tools (run without confirmation in the subagent; may have side effects; 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;
|
|
47
|
+
- a local-checkout path guard for file inspection.
|
|
48
|
+
|
|
49
|
+
The output is a markdown report: research target, key findings with citations, limitations, and next steps. 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
|
+
## API key
|
|
52
|
+
|
|
53
|
+
No API key is required. If `EXA_API_KEY` is set in the environment it is passed to Exa (`exaApiKey` query parameter) for higher rate limits. The key is never logged or echoed: it is redacted from error messages and is not shown to the model.
|
|
54
|
+
|
|
55
|
+
## Guarantees
|
|
56
|
+
|
|
57
|
+
- `fetch_url` accepts only `http://` and `https://` URLs; other schemes are rejected before any request.
|
|
58
|
+
- Each Exa request has a 30 second timeout; tool output is truncated (20,000 characters), and `fetch_url` page size is capped.
|
|
59
|
+
- Rate limits (HTTP 429 or Exa rate-limit errors) and network failures produce clear tool errors rather than silent empty results.
|
|
60
|
+
- Fetched content is treated as untrusted data; the prompt requires citations and forbids fabrication.
|
|
61
|
+
- Runtime guards block write/edit/bash tools and path traversal outside the checkout. Guards apply to built-in tools only; configured MCP tools pass the guard and are limited only by prompt guidance.
|
|
62
|
+
|
|
63
|
+
## Limitations
|
|
64
|
+
|
|
65
|
+
- The subagent has an 8-turn and 8-minute budget; tool use is disabled on the final turn so it can answer.
|
|
66
|
+
- Web access depends on the Exa hosted endpoint's availability and unauthenticated rate limits.
|
|
67
|
+
- Search and fetch contents are sent to Exa; do not include secrets in the research task.
|