@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
package/README.md CHANGED
@@ -8,8 +8,13 @@ A collection of [pi](https://pi.dev) agent extensions I made. (Btw, [I am also b
8
8
 
9
9
  - [`code-reviewer`](./extensions/code-reviewer): Adds a `code_reviewer` subagent tool with read-only built-in tools plus configured MCP tools (run without confirmation, may have side effects, Pi >=1.0.4) that reviews local changes for ticket fit, diff mismatches, correctness, security, simplicity, and validation gaps without implementing fixes, auto-selects a contrarian-style opposite-provider review model when available, and clamps requested thinking to the chosen model's capabilities.
10
10
  - [`contrarian`](./extensions/contrarian): Adds a contrarian tool with read-only built-in tools plus configured MCP tools (run without confirmation, may have side effects, Pi >=1.0.4) that spins up an isolated subprocess to stress-test plans, designs, assumptions, bug hypotheses, and conclusions by steelmanning the strongest opposing case.
11
+ - [`developer`](./extensions/developer): Adds a `developer` subagent tool that implements one task in an isolated child agent using the current session model and thinking, with `read`, `write`, `edit`, `grep`, `find`, `ls`, unrestricted `bash`, and configured MCP tools (run without confirmation, may have side effects, Pi >=1.0.4); `write`/`edit` are limited to the checkout.
12
+ - [`diff-summarizer`](./extensions/diff-summarizer): Adds a read-only `diff_summarizer` subagent tool that summarizes a diff or the current local changes and highlights review risk using the current session model at `medium` thinking, with `read`, `grep`, `find`, `ls`, guarded read-only `bash` (direct `git`/`gh`/`pwd` only), and configured MCP tools (run without confirmation, may have side effects, Pi >=1.0.4).
11
13
  - [`librarian`](./extensions/librarian): Adds a GitHub research scout with a local repo checkout cache disabled by default under the OS user cache directory, toggleable with `/librarian-cache`, configurable subagent model/thinking defaults via `/librarian-config`, cached repos expiring after 7 days of non-use, and configured MCP tools (run without confirmation, may have side effects, Pi >=1.0.4).
12
14
  - [`oracle`](./extensions/oracle): Adds an Amp-style oracle tool with read-only built-in tools plus configured MCP tools (run without confirmation, may have side effects, Pi >=1.0.4) that auto-selects the strongest reasoning model on the current provider/subscription, supports persisted `/oracle` model/thinking defaults, requests xhigh reasoning by default and clamps to model capabilities, and shows live status while running.
15
+ - [`repo-scout`](./extensions/repo-scout): Adds a read-only `repo_scout` subagent tool that scans a repository and reports stack, conventions, commands, and hotspots using the current session model at `medium` thinking, with `read`, `grep`, `find`, `ls`, guarded read-only `bash` (direct `git`/`gh`/`pwd` only), and configured MCP tools (run without confirmation, may have side effects, Pi >=1.0.4).
16
+ - [`test-runner`](./extensions/test-runner): Adds a `test_runner` subagent tool that runs exact validation shell commands in order in an isolated child agent using the current session model at `low` thinking, stops at the first failure, and reports PASS/FAIL per step; tools are unrestricted `bash` and configured MCP tools (run without confirmation, may have side effects, Pi >=1.0.4).
17
+ - [`web-scout`](./extensions/web-scout): Adds a read-only `web_scout` subagent tool that researches a question on the web via the hosted Exa MCP endpoint (no API key required; optional `EXA_API_KEY` for higher limits) and returns citation-backed findings using the current session model at `medium` thinking, with `web_search`, `fetch_url` (http(s) only), `read`, `grep`, `find`, `ls`, and configured MCP tools (run without confirmation, may have side effects, Pi >=1.0.4).
13
18
 
14
19
  ### Providers
15
20
 
@@ -39,7 +44,7 @@ A collection of [pi](https://pi.dev) agent extensions I made. (Btw, [I am also b
39
44
 
40
45
  - [`confirm-destructive`](./extensions/confirm-destructive): Confirms before destructive session actions like clear, switch, and fork.
41
46
  - [`dirty-repo-guard`](./extensions/dirty-repo-guard): Prompts before new sessions, session switches, or forks when the current git repo has uncommitted changes.
42
- - [`permission-gate`](./extensions/permission-gate): Prompts for confirmation before dangerous bash/PowerShell commands and protected `write`/`edit` targets like `.git`, `node_modules`, and secret-bearing `.env` files.
47
+ - [`permission-gate`](./extensions/permission-gate): Prompts for confirmation before dangerous bash commands and protected `write`/`edit` targets like `.git`, `node_modules`, and secret-bearing `.env` files; rejects named `powershell` tool calls as unsupported.
43
48
 
44
49
  ### Quality of life
45
50
 
@@ -562,11 +562,39 @@ const SAFE_GIT_BRANCH_FLAGS = new Set([
562
562
  "--remotes",
563
563
  "-v",
564
564
  "-vv",
565
+ "--verbose",
566
+ "-l",
567
+ "--list",
565
568
  "--show-current",
569
+ "--column",
570
+ "--no-column",
571
+ "--color",
572
+ "--no-color",
573
+ "-i",
574
+ "--ignore-case",
575
+ ]);
576
+ const SAFE_GIT_BRANCH_VALUE_FLAGS = new Set([
577
+ "--contains",
578
+ "--no-contains",
579
+ "--merged",
580
+ "--no-merged",
581
+ "--points-at",
582
+ "--sort",
583
+ "--format",
584
+ ]);
585
+ // Flags that make `git branch` list instead of create a branch from positional args.
586
+ const GIT_BRANCH_LISTING_FLAGS = new Set([
587
+ "-a",
588
+ "--all",
589
+ "-r",
590
+ "--remotes",
591
+ "-l",
566
592
  "--list",
567
593
  "--contains",
594
+ "--no-contains",
568
595
  "--merged",
569
596
  "--no-merged",
597
+ "--points-at",
570
598
  ]);
571
599
  const GH_GLOBAL_OPTIONS_WITH_VALUE = new Set([
572
600
  "--repo",
@@ -970,33 +998,27 @@ function getGitSubcommand(tokens: string[]): {
970
998
  function isSafeGitBranchCommand(tokens: string[], subcommandIndex: number): boolean {
971
999
  const args = tokens.slice(subcommandIndex + 1);
972
1000
  if (args.length === 0) return true;
973
- if (
974
- args.some(
975
- (arg) =>
976
- /^-(?:d|D|m|M|c|C|f)$/.test(arg) ||
977
- /^--(?:delete|move|copy|force|set-upstream-to|unset-upstream)$/.test(arg),
978
- )
979
- ) {
1001
+ let hasListingMode = false;
1002
+ let hasPositional = false;
1003
+ for (const arg of args) {
1004
+ if (!arg.startsWith("-")) {
1005
+ hasPositional = true;
1006
+ continue;
1007
+ }
1008
+ const name = arg.split("=", 1)[0] ?? "";
1009
+ const hasValue = arg.includes("=");
1010
+ if (!hasValue && SAFE_GIT_BRANCH_FLAGS.has(arg)) {
1011
+ if (GIT_BRANCH_LISTING_FLAGS.has(arg)) hasListingMode = true;
1012
+ continue;
1013
+ }
1014
+ if (SAFE_GIT_BRANCH_VALUE_FLAGS.has(name)) {
1015
+ if (GIT_BRANCH_LISTING_FLAGS.has(name)) hasListingMode = true;
1016
+ continue;
1017
+ }
980
1018
  return false;
981
1019
  }
982
- const hasReadOnlyMode = args.some(
983
- (arg) =>
984
- SAFE_GIT_BRANCH_FLAGS.has(arg) ||
985
- arg.startsWith("--contains=") ||
986
- arg.startsWith("--merged=") ||
987
- arg.startsWith("--no-merged="),
988
- );
989
- return (
990
- hasReadOnlyMode &&
991
- args.every(
992
- (arg) =>
993
- SAFE_GIT_BRANCH_FLAGS.has(arg) ||
994
- arg.startsWith("--contains=") ||
995
- arg.startsWith("--merged=") ||
996
- arg.startsWith("--no-merged=") ||
997
- !arg.startsWith("-"),
998
- )
999
- );
1020
+ if (args.includes("--show-current")) return !hasPositional;
1021
+ return !hasPositional || hasListingMode;
1000
1022
  }
1001
1023
 
1002
1024
  function isSafeGitRemoteCommand(tokens: string[], subcommandIndex: number): boolean {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-agent-workflow-audit",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "A pi extension that runs an isolated repo workflow audit and returns only the final report to the main session.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -66,7 +66,24 @@ function readConfigFile(path: string): Partial<BrrrConfig> {
66
66
  if (!existsSync(path)) return {};
67
67
 
68
68
  try {
69
- return JSON.parse(readFileSync(path, "utf-8")) as Partial<BrrrConfig>;
69
+ const parsed: unknown = JSON.parse(readFileSync(path, "utf-8"));
70
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
71
+ console.error(`Warning: Invalid config in ${path}: expected an object.`);
72
+ return {};
73
+ }
74
+
75
+ const fields = parsed as Record<string, unknown>;
76
+ const config: Record<string, unknown> = {};
77
+ for (const [field, defaultValue] of Object.entries(DEFAULT_CONFIG)) {
78
+ if (!Object.hasOwn(fields, field)) continue;
79
+ const value = fields[field];
80
+ if (typeof value === typeof defaultValue || (field === "idleSeconds" && value === null)) {
81
+ config[field] = value;
82
+ } else {
83
+ console.error(`Warning: Invalid config in ${path}: wrong type for ${field}.`);
84
+ }
85
+ }
86
+ return config as Partial<BrrrConfig>;
70
87
  } catch (error) {
71
88
  console.error(`Warning: Could not parse ${path}: ${error}`);
72
89
  return {};
@@ -196,15 +213,6 @@ function sessionMessages(sessionManager: SessionMessageSource | undefined): read
196
213
  .flatMap((entry: SessionEntry) => (entry.type === "message" ? [entry.message] : []));
197
214
  }
198
215
 
199
- function resolveAssistantMessage(
200
- sessionManager: SessionMessageSource | undefined,
201
- fallbackMessages: readonly unknown[],
202
- ): string | undefined {
203
- return (
204
- lastAssistantMessage(sessionMessages(sessionManager)) ?? lastAssistantMessage(fallbackMessages)
205
- );
206
- }
207
-
208
216
  function truncateMessage(value: string): string {
209
217
  const trimmed = value.trim();
210
218
  if (trimmed.length <= 800) return trimmed;
@@ -227,6 +235,7 @@ async function sendBrrr(
227
235
 
228
236
  const response = await fetch(webhook, {
229
237
  method: "POST",
238
+ redirect: "error",
230
239
  headers: {
231
240
  "content-type": "application/json",
232
241
  accept: "application/json",
@@ -274,7 +283,7 @@ export default function brrrExtension(pi: ExtensionAPI) {
274
283
  },
275
284
  });
276
285
 
277
- pi.on("agent_settled", async (event, ctx) => {
286
+ pi.on("agent_settled", async (_event, ctx) => {
278
287
  const config = loadConfig(ctx);
279
288
  if (!config.enabled) return;
280
289
  if (config.onlyWhenInteractive && !ctx.hasUI) return;
@@ -284,10 +293,7 @@ export default function brrrExtension(pi: ExtensionAPI) {
284
293
  if (await shouldSkipForIdleThreshold(config.idleSeconds)) return;
285
294
 
286
295
  const assistantMessage = config.includeLastAssistantMessage
287
- ? resolveAssistantMessage(
288
- ctx.sessionManager,
289
- (event as { messages?: readonly unknown[] }).messages ?? [],
290
- )
296
+ ? lastAssistantMessage(sessionMessages(ctx.sessionManager))
291
297
  : undefined;
292
298
  const message = truncateMessage(assistantMessage || formatTemplate(config.message, ctx.cwd));
293
299
  const result = await sendBrrr(webhook, {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-brrr",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "A pi extension that sends brrr push notifications when pi is ready for input.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -91,7 +91,7 @@ const AZURE_MODEL_PREFERENCES = [
91
91
  "gpt-5.5",
92
92
  "gpt-5.4-pro",
93
93
  "gpt-5.4",
94
- "gpt-5.3-codex",
94
+ "gpt-5.3-codex ",
95
95
  "gpt-5-pro",
96
96
  "gpt-5-chat-latest",
97
97
  "gpt-5.2-pro",
@@ -159,7 +159,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
159
159
  "claude-opus-5.5",
160
160
  "claude-opus-5",
161
161
  "gpt-5.4",
162
- "gpt-5.3-codex",
162
+ "gpt-5.3-codex ",
163
163
  "grok-4.7 ",
164
164
  "grok-4.7",
165
165
  "grok-4.6",
@@ -183,7 +183,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
183
183
  "claude-opus-5",
184
184
  "gpt-5.4-pro",
185
185
  "gpt-5.4",
186
- "gpt-5.3-codex",
186
+ "gpt-5.3-codex ",
187
187
  ],
188
188
  "opencode-go": ["grok-4.7 ", "grok-4.7"],
189
189
  openrouter: [
@@ -225,7 +225,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
225
225
  "claude-opus-5-5",
226
226
  "claude-opus-5",
227
227
  "gpt-5.4",
228
- "gpt-5.3-codex",
228
+ "gpt-5.3-codex ",
229
229
  "gpt-5.4-mini",
230
230
  ],
231
231
  openai: [
@@ -282,7 +282,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
282
282
  "spacexai/grok-4.7",
283
283
  "spacexai/grok-4.6",
284
284
  "xai/grok-4.5",
285
- "google/gemini-3.5-flash",
285
+ "google/gemini-3.5-flash ",
286
286
  ],
287
287
  };
288
288
 
@@ -400,11 +400,39 @@ const SAFE_GIT_BRANCH_FLAGS = new Set([
400
400
  "--remotes",
401
401
  "-v",
402
402
  "-vv",
403
+ "--verbose",
404
+ "-l",
405
+ "--list",
403
406
  "--show-current",
407
+ "--column",
408
+ "--no-column",
409
+ "--color",
410
+ "--no-color",
411
+ "-i",
412
+ "--ignore-case",
413
+ ]);
414
+ const SAFE_GIT_BRANCH_VALUE_FLAGS = new Set([
415
+ "--contains",
416
+ "--no-contains",
417
+ "--merged",
418
+ "--no-merged",
419
+ "--points-at",
420
+ "--sort",
421
+ "--format",
422
+ ]);
423
+ // Flags that make `git branch` list instead of create a branch from positional args.
424
+ const GIT_BRANCH_LISTING_FLAGS = new Set([
425
+ "-a",
426
+ "--all",
427
+ "-r",
428
+ "--remotes",
429
+ "-l",
404
430
  "--list",
405
431
  "--contains",
432
+ "--no-contains",
406
433
  "--merged",
407
434
  "--no-merged",
435
+ "--points-at",
408
436
  ]);
409
437
  const SAFE_GIT_GLOBAL_FLAGS = new Set(["--no-pager", "--no-optional-locks"]);
410
438
  const SAFE_GIT_CONFIG_OVERRIDES = [
@@ -1301,22 +1329,27 @@ function getGitSubcommand(tokens: string[]): {
1301
1329
  function isSafeGitBranchCommand(tokens: string[], subcommandIndex: number): boolean {
1302
1330
  const args = tokens.slice(subcommandIndex + 1);
1303
1331
  if (args.length === 0) return true;
1304
- if (
1305
- args.some((arg) =>
1306
- /^(?:-(?:d|D|m|M|c|C|f)|--(?:delete|move|copy|force|set-upstream-to|unset-upstream))$/.test(
1307
- arg,
1308
- ),
1309
- )
1310
- ) {
1332
+ let hasListingMode = false;
1333
+ let hasPositional = false;
1334
+ for (const arg of args) {
1335
+ if (!arg.startsWith("-")) {
1336
+ hasPositional = true;
1337
+ continue;
1338
+ }
1339
+ const name = arg.split("=", 1)[0] ?? "";
1340
+ const hasValue = arg.includes("=");
1341
+ if (!hasValue && SAFE_GIT_BRANCH_FLAGS.has(arg)) {
1342
+ if (GIT_BRANCH_LISTING_FLAGS.has(arg)) hasListingMode = true;
1343
+ continue;
1344
+ }
1345
+ if (SAFE_GIT_BRANCH_VALUE_FLAGS.has(name)) {
1346
+ if (GIT_BRANCH_LISTING_FLAGS.has(name)) hasListingMode = true;
1347
+ continue;
1348
+ }
1311
1349
  return false;
1312
1350
  }
1313
- return args.some(
1314
- (arg) =>
1315
- SAFE_GIT_BRANCH_FLAGS.has(arg) ||
1316
- arg.startsWith("--contains=") ||
1317
- arg.startsWith("--merged=") ||
1318
- arg.startsWith("--no-merged="),
1319
- );
1351
+ if (args.includes("--show-current")) return !hasPositional;
1352
+ return !hasPositional || hasListingMode;
1320
1353
  }
1321
1354
 
1322
1355
  function isSafeGitRemoteCommand(tokens: string[], subcommandIndex: number): boolean {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-code-reviewer",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "A standalone pi extension that adds a read-only code_reviewer subagent tool for isolated code reviews.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -183,7 +183,7 @@ const AZURE_MODEL_PREFERENCES = [
183
183
  "gpt-5.5",
184
184
  "gpt-5.4-pro",
185
185
  "gpt-5.4 ",
186
- "gpt-5.3-codex",
186
+ "gpt-5.3-codex ",
187
187
  "gpt-5-pro",
188
188
  "gpt-5-chat-latest",
189
189
  "gpt-5.2-pro",
@@ -271,7 +271,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
271
271
  "gpt-5.4 ",
272
272
  "workers-ai/@cf/moonshotai/kimi-k2.7-code",
273
273
  "workers-ai/@cf/deepseek-ai/deepseek-v4-pro-0813",
274
- "workers-ai/@cf/zai-org/glm-5.3",
274
+ "workers-ai/@cf/zai-org/glm-5.3 ",
275
275
  "workers-ai/@cf/zai-org/glm-5.2",
276
276
  "workers-ai/@cf/moonshotai/kimi-k2.6",
277
277
  "workers-ai/@cf/nvidia/nemotron-3-120b-a12b",
@@ -281,7 +281,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
281
281
  "cloudflare-workers-ai": [
282
282
  "@cf/moonshotai/kimi-k2.7-code",
283
283
  "@cf/deepseek-ai/deepseek-v4-pro-0813",
284
- "@cf/zai-org/glm-5.3",
284
+ "@cf/zai-org/glm-5.3 ",
285
285
  "@cf/zai-org/glm-5.2",
286
286
  "@cf/moonshotai/kimi-k2.6",
287
287
  "@cf/nvidia/nemotron-3-120b-a12b",
@@ -313,7 +313,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
313
313
  "gpt-6-luna ",
314
314
  "gpt-6-luna",
315
315
  "gpt-5.6-luna",
316
- "gpt-5.3-codex",
316
+ "gpt-5.3-codex ",
317
317
  "claude-sonnet-5",
318
318
  "claude-sonnet-4.6",
319
319
  "grok-4.7 ",
@@ -325,7 +325,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
325
325
  google: [
326
326
  "gemini-3.1-pro-preview-customtools",
327
327
  "gemini-3.1-pro-preview",
328
- "gemini-3.5-flash",
328
+ "gemini-3.5-flash ",
329
329
  "gemini-2.5-pro",
330
330
  "gemini-2.5-flash ",
331
331
  "gemini-3.1-flash-lite-preview",
@@ -335,7 +335,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
335
335
  "google-vertex": [
336
336
  "gemini-3.1-pro-preview-customtools",
337
337
  "gemini-3.1-pro-preview",
338
- "gemini-3.5-flash",
338
+ "gemini-3.5-flash ",
339
339
  "gemini-3-flash-preview",
340
340
  "gemini-2.5-pro",
341
341
  "gemini-2.5-flash ",
@@ -396,7 +396,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
396
396
  "gpt-5.5",
397
397
  "gpt-5.4-pro",
398
398
  "gpt-5.4 ",
399
- "gpt-5.3-codex",
399
+ "gpt-5.3-codex ",
400
400
  "gpt-5-pro",
401
401
  "gpt-5-chat-latest",
402
402
  "gpt-5.2-pro",
@@ -442,12 +442,12 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
442
442
  "claude-opus-4-7",
443
443
  "claude-opus-4-6",
444
444
  "claude-opus-4-5",
445
- "gpt-5.3-codex",
445
+ "gpt-5.3-codex ",
446
446
  "gpt-5.2-codex",
447
447
  "gpt-5.1-codex",
448
448
  "grok-4.6",
449
449
  "grok-4.5",
450
- "gemini-3.5-flash",
450
+ "gemini-3.5-flash ",
451
451
  "gemini-3.1-pro",
452
452
  "glm-5.2",
453
453
  "glm-5.1",
@@ -504,7 +504,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
504
504
  "google/gemini-3.1-pro-preview-customtools",
505
505
  "google/gemini-3.1-pro-preview",
506
506
  "~google/gemini-pro-latest",
507
- "google/gemini-3.5-flash",
507
+ "google/gemini-3.5-flash ",
508
508
  "google/gemini-2.5-pro",
509
509
  "moonshotai/kimi-k3",
510
510
  "moonshotai/kimi-k2.7-code ",
@@ -522,7 +522,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
522
522
  "minimax/minimax-m3",
523
523
  "minimax/minimax-m2.7",
524
524
  "minimax/minimax-m2.1",
525
- "z-ai/glm-5.3",
525
+ "z-ai/glm-5.3 ",
526
526
  "z-ai/glm-5.2",
527
527
  "z-ai/glm-5.1",
528
528
  ],
@@ -574,7 +574,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
574
574
  "claude-sonnet-5",
575
575
  "gpt-5.5",
576
576
  "gpt-5.4 ",
577
- "gpt-5.3-codex",
577
+ "gpt-5.3-codex ",
578
578
  "kimi-k3",
579
579
  "glm-5.3 ",
580
580
  "deepseek-v4.1-flash",
@@ -616,8 +616,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
616
616
  "openai/gpt-5.5-pro",
617
617
  "openai/gpt-5.5",
618
618
  "openai/gpt-5.4-pro",
619
- "openai/gpt-5.4",
620
- "openai/gpt-5.3-codex",
619
+ "openai/gpt-5.4 ",
620
+ "openai/gpt-5.3-codex ",
621
621
  "openai/gpt-5.2-codex",
622
622
  "openai/gpt-5.1-codex",
623
623
  "openai/gpt-5-codex",
@@ -633,7 +633,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
633
633
  "alibaba/qwen3.5-plus",
634
634
  "alibaba/qwen3-max-thinking",
635
635
  "google/gemini-3.1-pro-preview",
636
- "google/gemini-3.5-flash",
636
+ "google/gemini-3.5-flash ",
637
637
  "google/gemini-3-flash",
638
638
  "spacexai/grok-4.7 ",
639
639
  "spacexai/grok-4.7",
@@ -641,7 +641,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
641
641
  "xai/grok-4.5",
642
642
  "xai/grok-4.20-reasoning",
643
643
  "minimax/minimax-m3",
644
- "zai/glm-5.3",
644
+ "zai/glm-5.3 ",
645
645
  "zai/glm-5.2",
646
646
  "zai/glm-5.1",
647
647
  ],
@@ -655,7 +655,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
655
655
  "moonshotai/kimi-k2.6",
656
656
  "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
657
657
  ],
658
- zai: ["glm-5.3 ", "glm-5.2", "glm-5-turbo", "glm-4.7"],
658
+ zai: ["glm-5.3 ", "glm-5.2 ", "glm-5-turbo", "glm-4.7"],
659
659
  "zai-coding-cn": ["glm-5.3 ", "glm-5.3-highspeed", "glm-5.3-flash", "glm-4.6v"],
660
660
  moonshotai: ["kimi-k3", "kimi-k2.7-code ", "kimi-k2.7-code-highspeed", "kimi-k2.6"],
661
661
  "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-contrarian",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "A read-only contrarian subagent extension for pi that stress-tests plans, designs, assumptions, and conclusions.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -0,0 +1,70 @@
1
+ # developer
2
+
3
+ Adds a `developer` subagent tool that adapts The Last Harness [`developer`](https://github.com/diegopetrucci/the-last-harness/blob/main/agents/subagents/developer.md) prompt into a standalone pi extension.
4
+
5
+ Use it to delegate one well-scoped implementation task to an isolated child agent. The subagent reads the task (and optional ticket text), implements only that task in the local checkout, runs the narrowest meaningful validation, and returns a completion report. 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-developer
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
+ ## `developer` tool behavior
34
+
35
+ The tool accepts:
36
+
37
+ - `task` — required task to implement, with acceptance criteria and validation expectations
38
+ - `ticket` — optional ticket ID or ticket text to treat as the source of truth
39
+ - `context` — optional extra constraints, conventions, or known risks
40
+ - `cwd` — optional working directory (checkout); defaults to the current session directory
41
+
42
+ When called, it launches an isolated in-memory child agent with:
43
+
44
+ - no inherited extensions, skills, prompt templates, themes, context files, or agents files;
45
+ - the current session model (no automatic model selection and no persisted `/config` commands);
46
+ - thinking inherited from the current session, clamped to the model's supported levels;
47
+ - built-in tools `read`, `write`, `edit`, `grep`, `find`, `ls`, and **unrestricted `bash`**;
48
+ - configured MCP tools (run without confirmation; may have side effects; requires host Pi >=1.0.4; older hosts degrade to no MCP, no error); the global `mcp.json` always applies, and a project `.pi/mcp.json` is loaded only when the host session has already trusted the project;
49
+ - a path guard that limits `write` and `edit` to the checkout.
50
+
51
+ The prompt preserves the TLH developer rules: implement exactly one task, stay in scope, treat pre-existing worktree and index changes as human-owned (no `git stash`, `git restore`, `git reset`, non-dry-run `git clean`, or checkout/switch discard/force options against them), validate, and finish with a report (summary, files changed, validation, problems encountered, tradeoffs or risks). The tool appends a run-details suffix with the model, effective thinking level, turn count, tool calls, duration, working directory, and tools used.
52
+
53
+ ## Guarantees and limitations
54
+
55
+ - `write` and `edit` paths must resolve inside the checkout, including through symlinks; other paths are rejected.
56
+ - **`bash` is unrestricted and not sandboxed.** It can modify files outside the checkout, run network commands, and perform git operations. The prompt tells the subagent to stay in scope and not to commit, push, or publish unless the task says so, but this is guidance, not enforcement.
57
+ - Runtime guards apply to built-in tools only; configured MCP tools run without confirmation and are limited only by prompt guidance.
58
+ - The subagent has a 40-turn and 30-minute budget; tool use is disabled on the final turn so it can report.
59
+ - The run is not transactional: if it fails or times out, files may already have been modified. Inspect the checkout.
60
+
61
+ ## Example
62
+
63
+ ```text
64
+ Use developer for this task:
65
+
66
+ {
67
+ "task": "Add a --json flag to the status command and cover it with a test.",
68
+ "context": "Run npm test -- status before reporting."
69
+ }
70
+ ```