@diegopetrucci/pi-extensions 0.1.53 → 0.1.54

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 (68) hide show
  1. package/.pi-fleet-tested-version +1 -1
  2. package/README.md +35 -15
  3. package/extensions/agent-workflow-audit/.pi-fleet-tested-version +1 -1
  4. package/extensions/agent-workflow-audit/index.ts +12 -0
  5. package/extensions/agent-workflow-audit/package.json +1 -1
  6. package/extensions/annotate-git-diff/.pi-fleet-tested-version +1 -1
  7. package/extensions/annotate-git-diff/git.ts +1 -0
  8. package/extensions/annotate-git-diff/index.ts +1 -1
  9. package/extensions/annotate-git-diff/package.json +1 -1
  10. package/extensions/annotate-git-diff/prompt.ts +2 -1
  11. package/extensions/annotate-last-message/.pi-fleet-tested-version +1 -1
  12. package/extensions/annotate-last-message/index.ts +1 -1
  13. package/extensions/annotate-last-message/package.json +1 -1
  14. package/extensions/brrr/.pi-fleet-tested-version +1 -1
  15. package/extensions/brrr/index.ts +4 -3
  16. package/extensions/brrr/package.json +1 -1
  17. package/extensions/claude-fast/.pi-fleet-tested-version +1 -1
  18. package/extensions/claude-fast/package.json +1 -1
  19. package/extensions/confirm-destructive/.pi-fleet-tested-version +1 -1
  20. package/extensions/confirm-destructive/package.json +1 -1
  21. package/extensions/context-cap/.pi-fleet-tested-version +1 -1
  22. package/extensions/context-cap/package.json +1 -1
  23. package/extensions/context-inspector/.pi-fleet-tested-version +1 -1
  24. package/extensions/context-inspector/index.ts +5 -0
  25. package/extensions/context-inspector/package.json +1 -1
  26. package/extensions/contrarian/.pi-fleet-tested-version +1 -1
  27. package/extensions/contrarian/index.ts +35 -3
  28. package/extensions/contrarian/package.json +1 -1
  29. package/extensions/dirty-repo-guard/.pi-fleet-tested-version +1 -1
  30. package/extensions/dirty-repo-guard/package.json +1 -1
  31. package/extensions/git-footer/.pi-fleet-tested-version +1 -1
  32. package/extensions/git-footer/index.ts +42 -14
  33. package/extensions/git-footer/package.json +1 -1
  34. package/extensions/gnosis/.pi-fleet-tested-version +1 -1
  35. package/extensions/gnosis/package.json +1 -1
  36. package/extensions/illustrations-to-explain-things/.pi-fleet-tested-version +1 -1
  37. package/extensions/illustrations-to-explain-things/package.json +1 -1
  38. package/extensions/inline-bash/.pi-fleet-tested-version +1 -1
  39. package/extensions/inline-bash/package.json +1 -1
  40. package/extensions/librarian/.pi-fleet-tested-version +1 -1
  41. package/extensions/librarian/index.ts +7 -0
  42. package/extensions/librarian/package.json +1 -1
  43. package/extensions/minimal-footer/.pi-fleet-tested-version +1 -1
  44. package/extensions/minimal-footer/index.ts +132 -72
  45. package/extensions/minimal-footer/package.json +1 -1
  46. package/extensions/notify/.pi-fleet-tested-version +1 -1
  47. package/extensions/notify/package.json +1 -1
  48. package/extensions/openai-fast/.pi-fleet-tested-version +1 -1
  49. package/extensions/openai-fast/package.json +1 -1
  50. package/extensions/oracle/.pi-fleet-tested-version +1 -1
  51. package/extensions/oracle/index.ts +21 -2
  52. package/extensions/oracle/package.json +1 -1
  53. package/extensions/permission-gate/.pi-fleet-tested-version +1 -1
  54. package/extensions/permission-gate/index.ts +5 -1
  55. package/extensions/permission-gate/package.json +1 -1
  56. package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
  57. package/extensions/quiet-tools/index.ts +6 -0
  58. package/extensions/quiet-tools/package.json +1 -1
  59. package/extensions/review/.pi-fleet-tested-version +1 -1
  60. package/extensions/review/index.ts +251 -197
  61. package/extensions/review/package.json +1 -1
  62. package/extensions/todo/.pi-fleet-tested-version +1 -1
  63. package/extensions/todo/index.ts +1 -1
  64. package/extensions/todo/package.json +1 -1
  65. package/extensions/triage-comments/.pi-fleet-tested-version +1 -1
  66. package/extensions/triage-comments/index.ts +25 -3
  67. package/extensions/triage-comments/package.json +1 -1
  68. package/package.json +6 -5
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
package/README.md CHANGED
@@ -2,32 +2,52 @@
2
2
 
3
3
  [![Total npm downloads](https://img.shields.io/npm/dt/%40diegopetrucci%2Fpi-extensions?label=total%20npm%20downloads)](https://www.npmjs.com/package/@diegopetrucci/pi-extensions)
4
4
 
5
- A collection of [pi](https://github.com/earendil-works/pi-mono) agent extensions and skills I made.
5
+ A collection of [pi](https://pi.dev) agent extensions I made.
6
+
7
+ ### Subagents
8
+
9
+ - [`contrarian`](./extensions/contrarian): Adds a read-only contrarian tool that spins up an isolated subprocess to stress-test plans, designs, assumptions, bug hypotheses, and conclusions by steelmanning the strongest opposing case.
10
+ - [`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`, and cached repos expiring after 7 days of non-use.
11
+ - [`oracle`](./extensions/oracle): Adds an Amp-style read-only oracle tool 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.
12
+
13
+ ### Providers
14
+
15
+ - [`claude-fast`](./extensions/claude-fast): Adds `/claude-fast` to enable Anthropic Claude Fast mode for supported Claude Opus models by injecting `speed: "fast"`.
16
+ - [`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.
17
+
18
+ ### UI
19
+
20
+ - [`git-footer`](./extensions/git-footer): Standalone extension that adds TLH-style git dirty counts, ahead/behind, and optional PR number to pi's built-in footer status area.
21
+ - [`minimal-footer`](./extensions/minimal-footer): Replaces pi's built-in footer with a minimal configurable two-line layout: branch plus cached git dirty/ahead/PR status and repo on the first line, context/model on the second, optional `DUMB ZONE`, optional `xp` marker, plus OpenAI Codex 5-hour and 7-day usage when available.
22
+ - [`quiet-tools`](./extensions/quiet-tools): Renders collapsed built-in tool rows as a one-line invocation plus an expand hint without changing model-visible tool results; toggle temporarily with `/quiet-tools`.
23
+
24
+ ### / Slash commands
6
25
 
7
26
  - [`agent-workflow-audit`](./extensions/agent-workflow-audit): Adds `/agent-workflow-audit`, which runs an isolated repo workflow audit subagent and returns only the final distilled report to the main session.
8
27
  - [`annotate-git-diff`](./extensions/annotate-git-diff): Adds `/annotate-git-diff`, a native Glimpse UI for annotating git diffs and appending structured feedback prompts to the editor.
9
28
  - [`annotate-last-message`](./extensions/annotate-last-message): Adds `/annotate-last-message`, a native Glimpse UI for annotating the latest assistant reply and sending planning-oriented feedback back through the editor.
10
- - [`brrr`](./extensions/brrr): Sends brrr push notifications when pi finishes an agent turn and is ready for input, with optional macOS idle gating.
11
- - [`claude-fast`](./extensions/claude-fast): Adds `/claude-fast` to enable Anthropic Claude Fast mode for supported Claude Opus models by injecting `speed: "fast"`.
12
- - [`confirm-destructive`](./extensions/confirm-destructive): Confirms before destructive session actions like clear, switch, and fork.
13
- - [`contrarian`](./extensions/contrarian): Adds a read-only contrarian tool that spins up an isolated subprocess to stress-test plans, designs, assumptions, bug hypotheses, and conclusions by steelmanning the strongest opposing case.
29
+ - [`review`](./extensions/review): Adds `/review` and `/end-review` for interactive code reviews of local changes, commits, PRs, and selected paths.
30
+ - [`triage-comments`](./extensions/triage-comments): Adds `/triage-comments` and a read-only `triage_comments` subagent tool that can auto-detect the current branch's PR, filter resolved/outdated inline comments, classify selected review comments with evidence, and suggest handling options without implementing changes
31
+
32
+ ### Context management
33
+
14
34
  - [`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`.
15
35
  - [`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.
36
+
37
+ ### Safety
38
+
39
+ - [`confirm-destructive`](./extensions/confirm-destructive): Confirms before destructive session actions like clear, switch, and fork.
16
40
  - [`dirty-repo-guard`](./extensions/dirty-repo-guard): Prompts before new sessions, session switches, or forks when the current git repo has uncommitted changes.
17
- - [`git-footer`](./extensions/git-footer): Standalone extension that adds TLH-style git dirty counts, ahead/behind, and optional PR number to pi's built-in footer status area.
41
+ - [`permission-gate`](./extensions/permission-gate): Prompts for confirmation before dangerous bash commands like `rm -rf`, `sudo`, and `chmod 777`.
42
+
43
+ ### Quality of life
44
+
45
+ - [`brrr`](./extensions/brrr): Sends brrr push notifications when pi finishes an agent turn and is ready for input, with optional macOS idle gating.
18
46
  - [`gnosis`](./extensions/gnosis): Exposes the `gn` repo-local knowledge base CLI as an agent tool for searching and recording durable project decisions, constraints, and intent.
19
47
  - [`illustrations-to-explain-things`](./extensions/illustrations-to-explain-things): Adds a skill for generating clean, absurd Xiaohei-style article illustrations, shot lists, image edits, and visual metaphors.
20
48
  - [`inline-bash`](./extensions/inline-bash): Expands `!{command}` snippets in user prompts by running them through bash before the prompt reaches the agent.
21
- - [`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`, and cached repos expiring after 7 days of non-use.
22
- - [`minimal-footer`](./extensions/minimal-footer): Replaces pi's built-in footer with a minimal configurable two-line layout: branch plus cached git dirty/ahead/PR status and repo on the first line, context/model on the second, optional `DUMB ZONE`, optional `xp` marker, plus OpenAI Codex 5-hour and 7-day usage when available.
23
49
  - [`notify`](./extensions/notify): Sends configurable terminal, desktop, bell, and sound notifications when pi finishes and is ready for input.
24
- - [`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.
25
- - [`oracle`](./extensions/oracle): Adds an Amp-style read-only oracle tool 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.
26
- - [`permission-gate`](./extensions/permission-gate): Prompts for confirmation before dangerous bash commands like `rm -rf`, `sudo`, and `chmod 777`.
27
- - [`quiet-tools`](./extensions/quiet-tools): Renders collapsed built-in tool rows as a one-line invocation plus an expand hint without changing model-visible tool results; toggle temporarily with `/quiet-tools`.
28
- - [`review`](./extensions/review): Adds `/review` and `/end-review` for interactive code reviews of local changes, commits, PRs, and selected paths.
29
- - [`todo`](./extensions/todo): Adds a branch-aware `todo` tool for the agent and a `/todos` viewer for users.
30
- - [`triage-comments`](./extensions/triage-comments): Adds `/triage-comments` and a read-only `triage_comments` subagent tool that can auto-detect the current branch's PR, filter resolved/outdated inline comments, classify selected review comments with evidence, and suggest handling options without implementing changes.
50
+ - [`todo`](./extensions/todo): Adds a branch-aware `todo` tool for the agent and a `/todos` viewer for users..
31
51
 
32
52
  (For the full list of pi extensions I use, [check out my dotfiles](https://github.com/diegopetrucci/dot/blob/main/.pi/agent/settings.json).)
33
53
 
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -1103,6 +1103,18 @@ async function runAudit(
1103
1103
  }
1104
1104
  }
1105
1105
 
1106
+ export const __test__ = {
1107
+ appendRunBoundary,
1108
+ assertToolPathInsideCwd,
1109
+ buildSystemPrompt,
1110
+ buildUserPrompt,
1111
+ createAuditRuntimeGuardExtension,
1112
+ formatToolCall,
1113
+ getBlockedBashReason,
1114
+ parseArgs,
1115
+ renderCollapsedReport,
1116
+ };
1117
+
1106
1118
  export default function agentWorkflowAuditExtension(pi: ExtensionAPI) {
1107
1119
  pi.registerMessageRenderer(CUSTOM_TYPE, (message, { expanded }, theme) => {
1108
1120
  const details = message.details as AuditDetails | undefined;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-agent-workflow-audit",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -938,6 +938,7 @@ export async function loadReviewFileContents(
938
938
  }
939
939
 
940
940
  export const __testing = {
941
+ parseNameStatus,
941
942
  parseStatusPorcelainZ,
942
943
  shouldNormalizeBranchChanges,
943
944
  };
@@ -94,7 +94,7 @@ export default function (pi: ExtensionAPI) {
94
94
  }
95
95
 
96
96
  async function reviewRepository(ctx: ExtensionCommandContext): Promise<void> {
97
- if (!ctx.hasUI) {
97
+ if (ctx.mode !== "tui") {
98
98
  ctx.ui.notify("annotate-git-diff requires interactive mode.", "error");
99
99
  return;
100
100
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-annotate-git-diff",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "A standalone pi extension that adds /annotate-git-diff, a native Glimpse UI for annotating git diffs and sending feedback to the editor.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -43,6 +43,7 @@ function formatLocation(comment: DiffReviewComment, file: ReviewFile | undefined
43
43
 
44
44
  export function composeReviewPrompt(files: ReviewFile[], payload: ReviewSubmitPayload): string {
45
45
  const fileMap = new Map(files.map((file) => [file.id, file]));
46
+ const comments = payload.comments.filter((comment) => comment.body.trim().length > 0);
46
47
  const lines: string[] = [];
47
48
 
48
49
  lines.push("Please address the following feedback");
@@ -54,7 +55,7 @@ export function composeReviewPrompt(files: ReviewFile[], payload: ReviewSubmitPa
54
55
  lines.push("");
55
56
  }
56
57
 
57
- payload.comments.forEach((comment, index) => {
58
+ comments.forEach((comment, index) => {
58
59
  const file = fileMap.get(comment.fileId);
59
60
  lines.push(`${index + 1}. ${formatLocation(comment, file)}`);
60
61
  lines.push(` ${comment.body.trim()}`);
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -38,7 +38,7 @@ export function registerAnnotateLastMessageCommand(pi: ExtensionAPI): void {
38
38
  }
39
39
 
40
40
  async function openAnnotationWindow(ctx: ExtensionCommandContext): Promise<void> {
41
- if (!ctx.hasUI) {
41
+ if (ctx.mode !== "tui") {
42
42
  ctx.ui.notify("annotate-last-message requires interactive mode.", "error");
43
43
  return;
44
44
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-annotate-last-message",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "A standalone pi extension that adds /annotate-last-message, a native Glimpse UI for annotating the latest assistant reply.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -159,8 +159,9 @@ function extractTextContent(content: unknown): string {
159
159
  }
160
160
  if (typeof part !== "object" || part === null) continue;
161
161
 
162
- const maybeText = (part as { text?: unknown }).text;
163
- if (typeof maybeText === "string") parts.push(maybeText);
162
+ const typedPart = part as { type?: unknown; text?: unknown };
163
+ if (typedPart.type !== undefined && typedPart.type !== "text") continue;
164
+ if (typeof typedPart.text === "string") parts.push(typedPart.text);
164
165
  }
165
166
 
166
167
  return parts.join("\n");
@@ -173,7 +174,7 @@ function lastAssistantMessage(messages: readonly unknown[]): string | undefined
173
174
  if ((message as { role?: unknown }).role !== "assistant") continue;
174
175
 
175
176
  const text = extractTextContent((message as { content?: unknown }).content).trim();
176
- if (text) return text;
177
+ return text || undefined;
177
178
  }
178
179
 
179
180
  return undefined;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-brrr",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A pi extension that sends brrr push notifications when pi is ready for input.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-claude-fast",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A pi extension that enables Anthropic Claude Fast mode for supported Claude Opus models by injecting speed=fast.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-confirm-destructive",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A pi extension that confirms destructive session actions.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-context-cap",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A pi extension that caps effective model context windows at 200k tokens for earlier auto-compaction.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -1447,6 +1447,11 @@ pre { margin: 0; white-space: pre-wrap; word-break: break-word; color: #cbd5e1;
1447
1447
  </html>`;
1448
1448
  }
1449
1449
 
1450
+ export const __testing = {
1451
+ analyzeEntries,
1452
+ buildReportData,
1453
+ };
1454
+
1450
1455
  export default function contextInspectorExtension(pi: ExtensionAPI) {
1451
1456
  pi.registerCommand("context", {
1452
1457
  description: "Open a local HTML breakdown of where this session's context is going",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-context-inspector",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A pi extension that opens a local HTML dashboard explaining where the current session context is going.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -72,7 +72,20 @@ const COLLAPSED_LINE_LIMIT = 8;
72
72
  const CONTRARIAN_STATUS_ID = "contrarian";
73
73
  const CONTRARIAN_WIDGET_ID = "contrarian";
74
74
  const CONTRARIAN_CONFIG_FILE = "contrarian.json";
75
- const CONTRARIAN_MODEL_PREFERENCES = ["gpt-5.5", "claude-opus-4-8", "claude-opus-4.8"];
75
+ const CONTRARIAN_MODEL_PREFERENCES = [
76
+ "gpt-5.5",
77
+ "claude-opus-4-8",
78
+ "claude-opus-4.8",
79
+ "claude-sonnet-5-0",
80
+ "claude-sonnet-5.0",
81
+ "claude-sonnet-5",
82
+ "claude-sonnet-4-6",
83
+ "claude-sonnet-4.6",
84
+ "claude-sonnet-4-5",
85
+ "claude-sonnet-4.5",
86
+ "claude-sonnet-4-0",
87
+ "claude-sonnet-4",
88
+ ];
76
89
 
77
90
  const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
78
91
  "amazon-bedrock": [
@@ -83,6 +96,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
83
96
  "claude-opus-4-5",
84
97
  "claude-opus-4-1",
85
98
  "claude-opus-4",
99
+ "claude-sonnet-5",
86
100
  "claude-sonnet-4-6",
87
101
  "claude-sonnet-4-5",
88
102
  "claude-sonnet-4",
@@ -107,6 +121,9 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
107
121
  "claude-opus-4.1",
108
122
  "claude-opus-4-0",
109
123
  "claude-opus-4",
124
+ "claude-sonnet-5-0",
125
+ "claude-sonnet-5.0",
126
+ "claude-sonnet-5",
110
127
  "claude-sonnet-4-6",
111
128
  "claude-sonnet-4.6",
112
129
  "claude-sonnet-4-5",
@@ -185,6 +202,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
185
202
  "gpt-5",
186
203
  "gemini-3.1-pro-preview",
187
204
  "gemini-3-pro-preview",
205
+ "claude-sonnet-5.0",
206
+ "claude-sonnet-5",
188
207
  "claude-sonnet-4.6",
189
208
  "claude-sonnet-4.5",
190
209
  "gemini-2.5-pro",
@@ -360,6 +379,8 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
360
379
  "anthropic/claude-opus-4.6",
361
380
  "anthropic/claude-opus-4.5",
362
381
  "anthropic/claude-opus-4.1",
382
+ "anthropic/claude-sonnet-5.0",
383
+ "anthropic/claude-sonnet-5",
363
384
  "anthropic/claude-sonnet-4.6",
364
385
  "openai/gpt-5.5-pro",
365
386
  "openai/gpt-5.5",
@@ -1184,6 +1205,13 @@ function renderCollapsedText(text: string, lineLimit = COLLAPSED_LINE_LIMIT): st
1184
1205
  return [...lines.slice(0, lineLimit), `... (${lines.length - lineLimit} more lines)`].join("\n");
1185
1206
  }
1186
1207
 
1208
+ export const __test__ = {
1209
+ findAvailableModel,
1210
+ parseModelPreference,
1211
+ resolveThinkingLevel,
1212
+ selectContrarianModel,
1213
+ };
1214
+
1187
1215
  export default function contrarianExtension(pi: ExtensionAPI) {
1188
1216
  const activeRuns = new Map<string, ContrarianUiRun>();
1189
1217
  let preferences: ContrarianPreferences = {};
@@ -1347,13 +1375,17 @@ export default function contrarianExtension(pi: ExtensionAPI) {
1347
1375
  parameters: ContrarianParams,
1348
1376
 
1349
1377
  async execute(toolCallId, params, signal, onUpdate, ctx) {
1378
+ const rawTask = (params as { task?: unknown }).task;
1379
+ const task = typeof rawTask === "string" ? rawTask.trim() : "";
1380
+ if (!task) throw new Error("Invalid parameters: expected task to be a non-empty string.");
1381
+
1350
1382
  const explicitModel = parseModelPreference(params.model);
1351
1383
  const defaultModel = parseModelPreference(preferences.model);
1352
1384
  const configuredModel = explicitModel.model ?? defaultModel.model;
1353
1385
  const configuredThinkingLevel =
1354
1386
  params.thinkingLevel ?? explicitModel.thinkingLevel ?? preferences.thinkingLevel ?? defaultModel.thinkingLevel;
1355
1387
  const uiRun: ContrarianUiRun = {
1356
- task: params.task,
1388
+ task,
1357
1389
  includeBash: params.includeBash ?? false,
1358
1390
  startedAt: Date.now(),
1359
1391
  };
@@ -1445,7 +1477,7 @@ export default function contrarianExtension(pi: ExtensionAPI) {
1445
1477
  uiRun.selection = attempt;
1446
1478
  updateContrarianUi(ctx, activeRuns);
1447
1479
 
1448
- const result = await runContrarian(attempt, params, signal, handleUpdate, ctx.cwd);
1480
+ const result = await runContrarian(attempt, { ...params, task }, signal, handleUpdate, ctx.cwd);
1449
1481
  if (result.ok) {
1450
1482
  return {
1451
1483
  content: [{ type: "text", text: result.output }],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-contrarian",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-dirty-repo-guard",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A pi extension that prompts before session changes when the current git repo has uncommitted changes.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -31,6 +31,11 @@ type CommandRunner = (
31
31
  options: { cwd: string; signal: AbortSignal },
32
32
  ) => Promise<CommandResult>;
33
33
 
34
+ type RunCommandSafelyResult =
35
+ | { kind: "ok"; result: CommandResult }
36
+ | { kind: "transient" }
37
+ | { kind: "unavailable" };
38
+
34
39
  type TimerHandle = unknown;
35
40
 
36
41
  type Clock = {
@@ -286,6 +291,10 @@ function defaultClock(): Clock {
286
291
  };
287
292
  }
288
293
 
294
+ function isCommandUnavailableError(error: unknown): boolean {
295
+ return !!error && typeof error === "object" && (error as { code?: unknown }).code === "ENOENT";
296
+ }
297
+
289
298
  class GitFooterCache {
290
299
  private readonly cwd: () => string;
291
300
  private readonly runner: CommandRunner;
@@ -345,7 +354,7 @@ class GitFooterCache {
345
354
  const result = await this.fetchGitStatus();
346
355
  if (this.disposed) return;
347
356
  if (result.kind === "transient") return;
348
- if (result.kind === "not-a-repo") {
357
+ if (result.kind === "not-a-repo" || result.kind === "unavailable") {
349
358
  this.statusSnapshot = undefined;
350
359
  this.pullRequestSnapshot = undefined;
351
360
  this.lastSeenBranch = undefined;
@@ -371,8 +380,8 @@ class GitFooterCache {
371
380
 
372
381
  const pr = await this.fetchPullRequest();
373
382
  if (this.disposed) return;
374
- if (pr !== undefined) this.pullRequestSnapshot = pr;
375
- else if (branchChanged) this.pullRequestSnapshot = undefined;
383
+ if (pr.kind === "ok") this.pullRequestSnapshot = pr.pullRequest;
384
+ else if (pr.kind === "not-found" || pr.kind === "unavailable") this.pullRequestSnapshot = undefined;
376
385
 
377
386
  this.emitChangeIfSnapshotsChanged(previousStatusSnapshot, previousPullRequestSnapshot);
378
387
  }
@@ -399,32 +408,40 @@ class GitFooterCache {
399
408
  | { kind: "ok"; status: GitStatusSnapshot }
400
409
  | { kind: "not-a-repo" }
401
410
  | { kind: "transient" }
411
+ | { kind: "unavailable" }
402
412
  > {
403
413
  const result = await this.runCommandSafely("git", GIT_STATUS_ARGS, this.gitTimeoutMs);
404
- if (!result) return { kind: "transient" };
405
- if (result.exitCode !== 0) return { kind: "not-a-repo" };
406
- return { kind: "ok", status: parseGitStatusPorcelainV2(result.stdout) };
414
+ if (result.kind !== "ok") return result;
415
+ if (result.result.exitCode !== 0) return { kind: "not-a-repo" };
416
+ return { kind: "ok", status: parseGitStatusPorcelainV2(result.result.stdout) };
407
417
  }
408
418
 
409
- private async fetchPullRequest(): Promise<PullRequestSnapshot | undefined> {
419
+ private async fetchPullRequest(): Promise<
420
+ | { kind: "ok"; pullRequest: PullRequestSnapshot | undefined }
421
+ | { kind: "not-found" }
422
+ | { kind: "transient" }
423
+ | { kind: "unavailable" }
424
+ > {
410
425
  const result = await this.runCommandSafely("gh", GH_PR_VIEW_ARGS, this.ghTimeoutMs);
411
- if (!result || result.exitCode !== 0) return undefined;
412
- return parsePullRequestJson(result.stdout);
426
+ if (result.kind !== "ok") return result;
427
+ if (result.result.exitCode !== 0) return { kind: "not-found" };
428
+ return { kind: "ok", pullRequest: parsePullRequestJson(result.result.stdout) };
413
429
  }
414
430
 
415
431
  private async runCommandSafely(
416
432
  command: string,
417
433
  args: readonly string[],
418
434
  timeoutMs: number,
419
- ): Promise<CommandResult | undefined> {
420
- if (this.disposed) return undefined;
435
+ ): Promise<RunCommandSafelyResult> {
436
+ if (this.disposed) return { kind: "transient" };
421
437
  const controller = new AbortController();
422
438
  this.inflightControllers.add(controller);
423
439
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
424
440
  try {
425
- return await this.runner(command, args, { cwd: this.cwd(), signal: controller.signal });
426
- } catch {
427
- return undefined;
441
+ const result = await this.runner(command, args, { cwd: this.cwd(), signal: controller.signal });
442
+ return { kind: "ok", result };
443
+ } catch (error) {
444
+ return isCommandUnavailableError(error) ? { kind: "unavailable" } : { kind: "transient" };
428
445
  } finally {
429
446
  clearTimeout(timeoutId);
430
447
  this.inflightControllers.delete(controller);
@@ -478,3 +495,14 @@ export default function (pi: ExtensionAPI) {
478
495
  ctx.ui.setStatus(STATUS_KEY, undefined);
479
496
  });
480
497
  }
498
+
499
+ export const __testing = {
500
+ GitFooterCache,
501
+ GIT_STATUS_ARGS,
502
+ GH_PR_VIEW_ARGS,
503
+ parseGitStatusPorcelainV2,
504
+ parsePullRequestJson,
505
+ formatGitStatusFooterSegment,
506
+ formatPullRequestFooterSegment,
507
+ formatGitFooterStatus,
508
+ };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-git-footer",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "A TLH-style git status add-on for pi's built-in footer.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-gnosis",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A pi extension that exposes the gnosis repo-local knowledge base CLI as an agent tool.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-illustrations-to-explain-things",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "A pi skill for generating clean, absurd Xiaohei-style article illustrations, shot lists, and visual metaphors.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-inline-bash",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A pi extension that expands inline bash commands in user prompts.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3
@@ -777,6 +777,13 @@ function isAbortLikeError(error: unknown): boolean {
777
777
  return /aborted|cancelled|canceled/i.test(message);
778
778
  }
779
779
 
780
+ export const __test__ = {
781
+ buildLibrarianCandidates,
782
+ findAvailableModel,
783
+ isModelAvailabilityError,
784
+ parseModelPreference,
785
+ };
786
+
780
787
  export default function librarianExtension(pi: ExtensionAPI) {
781
788
  let cachePreference: CacheMode = DEFAULT_CACHE_MODE;
782
789
  let modelPreference: string | undefined;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-librarian",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
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",
@@ -1 +1 @@
1
- 0.79.10
1
+ 0.80.3