@diegopetrucci/pi-extensions 0.1.58 → 0.1.60

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 (70) hide show
  1. package/.pi-fleet-tested-version +1 -1
  2. package/extensions/agent-workflow-audit/.pi-fleet-tested-version +1 -1
  3. package/extensions/agent-workflow-audit/package.json +1 -1
  4. package/extensions/annotate-git-diff/.pi-fleet-tested-version +1 -1
  5. package/extensions/annotate-git-diff/package.json +1 -1
  6. package/extensions/annotate-last-message/.pi-fleet-tested-version +1 -1
  7. package/extensions/annotate-last-message/package.json +1 -1
  8. package/extensions/brrr/.pi-fleet-tested-version +1 -1
  9. package/extensions/brrr/package.json +1 -1
  10. package/extensions/claude-fast/.pi-fleet-tested-version +1 -1
  11. package/extensions/claude-fast/package.json +1 -1
  12. package/extensions/code-reviewer/.pi-fleet-tested-version +1 -1
  13. package/extensions/code-reviewer/index.ts +120 -37
  14. package/extensions/code-reviewer/package.json +1 -1
  15. package/extensions/confirm-destructive/.pi-fleet-tested-version +1 -1
  16. package/extensions/confirm-destructive/README.md +1 -1
  17. package/extensions/confirm-destructive/index.ts +20 -5
  18. package/extensions/confirm-destructive/package.json +1 -1
  19. package/extensions/context-cap/.pi-fleet-tested-version +1 -1
  20. package/extensions/context-cap/package.json +1 -1
  21. package/extensions/context-inspector/.pi-fleet-tested-version +1 -1
  22. package/extensions/context-inspector/package.json +1 -1
  23. package/extensions/contrarian/.pi-fleet-tested-version +1 -1
  24. package/extensions/contrarian/README.md +1 -1
  25. package/extensions/contrarian/index.ts +175 -32
  26. package/extensions/contrarian/package.json +1 -1
  27. package/extensions/dirty-repo-guard/.pi-fleet-tested-version +1 -1
  28. package/extensions/dirty-repo-guard/README.md +1 -1
  29. package/extensions/dirty-repo-guard/package.json +1 -1
  30. package/extensions/dynamic-context-pruning/.pi-fleet-tested-version +1 -1
  31. package/extensions/dynamic-context-pruning/package.json +1 -1
  32. package/extensions/git-footer/.pi-fleet-tested-version +1 -1
  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/README.md +1 -1
  40. package/extensions/inline-bash/package.json +1 -1
  41. package/extensions/librarian/.pi-fleet-tested-version +1 -1
  42. package/extensions/librarian/index.ts +385 -106
  43. package/extensions/librarian/package.json +1 -1
  44. package/extensions/minimal-footer/.pi-fleet-tested-version +1 -1
  45. package/extensions/minimal-footer/package.json +1 -1
  46. package/extensions/notify/.pi-fleet-tested-version +1 -1
  47. package/extensions/notify/README.md +1 -1
  48. package/extensions/notify/package.json +1 -1
  49. package/extensions/openai-fast/.pi-fleet-tested-version +1 -1
  50. package/extensions/openai-fast/package.json +1 -1
  51. package/extensions/oracle/.pi-fleet-tested-version +1 -1
  52. package/extensions/oracle/README.md +1 -1
  53. package/extensions/oracle/index.ts +176 -33
  54. package/extensions/oracle/package.json +1 -1
  55. package/extensions/permission-gate/.pi-fleet-tested-version +1 -1
  56. package/extensions/permission-gate/README.md +1 -1
  57. package/extensions/permission-gate/index.ts +453 -2
  58. package/extensions/permission-gate/package.json +1 -1
  59. package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
  60. package/extensions/quiet-tools/package.json +1 -1
  61. package/extensions/review/.pi-fleet-tested-version +1 -1
  62. package/extensions/review/index.ts +53 -19
  63. package/extensions/review/package.json +1 -1
  64. package/extensions/todo/.pi-fleet-tested-version +1 -1
  65. package/extensions/todo/README.md +1 -1
  66. package/extensions/todo/package.json +1 -1
  67. package/extensions/triage-comments/.pi-fleet-tested-version +1 -1
  68. package/extensions/triage-comments/index.ts +107 -33
  69. package/extensions/triage-comments/package.json +1 -1
  70. package/package.json +4 -4
@@ -2,8 +2,8 @@ import { spawn } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
3
  import * as fs from "node:fs/promises";
4
4
  import * as path from "node:path";
5
- import { StringEnum } from "@earendil-works/pi-ai";
6
- import { getAgentDir, getMarkdownTheme, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
5
+ import { StringEnum, type Usage } from "@earendil-works/pi-ai";
6
+ import { getAgentDir, getMarkdownTheme, type AgentToolResult, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
7
7
  import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
8
8
  import { Type } from "typebox";
9
9
 
@@ -25,7 +25,14 @@ interface UsageStats {
25
25
  output: number;
26
26
  cacheRead: number;
27
27
  cacheWrite: number;
28
+ cacheWrite1h?: number;
29
+ reasoning?: number;
30
+ totalTokens: number;
28
31
  cost: number;
32
+ costInput: number;
33
+ costOutput: number;
34
+ costCacheRead: number;
35
+ costCacheWrite: number;
29
36
  turns: number;
30
37
  contextTokens: number;
31
38
  }
@@ -464,7 +471,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
464
471
  "moonshotai/kimi-k2.6",
465
472
  "z-ai/glm-5.2",
466
473
  "minimaxai/minimax-m3",
467
- "mistralai/mistral-large-3-675b-instruct-2512",
468
474
  "openai/gpt-oss-120b",
469
475
  "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
470
476
  "nvidia/nemotron-3-nano-30b-a3b",
@@ -537,12 +543,91 @@ function createEmptyUsage(): UsageStats {
537
543
  output: 0,
538
544
  cacheRead: 0,
539
545
  cacheWrite: 0,
546
+ totalTokens: 0,
540
547
  cost: 0,
548
+ costInput: 0,
549
+ costOutput: 0,
550
+ costCacheRead: 0,
551
+ costCacheWrite: 0,
541
552
  turns: 0,
542
553
  contextTokens: 0,
543
554
  };
544
555
  }
545
556
 
557
+ function cloneUsageStats(stats: UsageStats): UsageStats {
558
+ return { ...stats };
559
+ }
560
+
561
+ function mergeUsageStats(target: UsageStats, source: UsageStats | undefined): UsageStats {
562
+ if (!source) return target;
563
+ target.input += source.input;
564
+ target.output += source.output;
565
+ target.cacheRead += source.cacheRead;
566
+ target.cacheWrite += source.cacheWrite;
567
+ if (source.cacheWrite1h !== undefined) {
568
+ target.cacheWrite1h = (target.cacheWrite1h ?? 0) + source.cacheWrite1h;
569
+ }
570
+ if (source.reasoning !== undefined) {
571
+ target.reasoning = (target.reasoning ?? 0) + source.reasoning;
572
+ }
573
+ target.totalTokens += source.totalTokens;
574
+ target.cost += source.cost;
575
+ target.costInput += source.costInput;
576
+ target.costOutput += source.costOutput;
577
+ target.costCacheRead += source.costCacheRead;
578
+ target.costCacheWrite += source.costCacheWrite;
579
+ target.turns += source.turns;
580
+ target.contextTokens = source.contextTokens || target.contextTokens;
581
+ return target;
582
+ }
583
+
584
+ function usageStatsToToolUsage(stats: UsageStats): Usage {
585
+ return {
586
+ input: stats.input,
587
+ output: stats.output,
588
+ cacheRead: stats.cacheRead,
589
+ cacheWrite: stats.cacheWrite,
590
+ ...(stats.cacheWrite1h !== undefined ? { cacheWrite1h: stats.cacheWrite1h } : {}),
591
+ ...(stats.reasoning !== undefined ? { reasoning: stats.reasoning } : {}),
592
+ totalTokens: stats.totalTokens,
593
+ cost: {
594
+ input: stats.costInput,
595
+ output: stats.costOutput,
596
+ cacheRead: stats.costCacheRead,
597
+ cacheWrite: stats.costCacheWrite,
598
+ total: stats.cost,
599
+ },
600
+ };
601
+ }
602
+
603
+ function addUsageStats(
604
+ target: UsageStats,
605
+ source: Usage | undefined,
606
+ options: { countTurn?: boolean; updateContextTokens?: boolean } = {},
607
+ ): void {
608
+ if (!source) return;
609
+ if (options.countTurn) target.turns += 1;
610
+ target.input += source.input || 0;
611
+ target.output += source.output || 0;
612
+ target.cacheRead += source.cacheRead || 0;
613
+ target.cacheWrite += source.cacheWrite || 0;
614
+ if (source.cacheWrite1h !== undefined) {
615
+ target.cacheWrite1h = (target.cacheWrite1h ?? 0) + source.cacheWrite1h;
616
+ }
617
+ if (source.reasoning !== undefined) {
618
+ target.reasoning = (target.reasoning ?? 0) + source.reasoning;
619
+ }
620
+ target.totalTokens += source.totalTokens || 0;
621
+ target.cost += source.cost?.total || 0;
622
+ target.costInput += source.cost?.input || 0;
623
+ target.costOutput += source.cost?.output || 0;
624
+ target.costCacheRead += source.cost?.cacheRead || 0;
625
+ target.costCacheWrite += source.cost?.cacheWrite || 0;
626
+ if (options.updateContextTokens) {
627
+ target.contextTokens = source.totalTokens || target.contextTokens;
628
+ }
629
+ }
630
+
546
631
  function getContrarianConfigPath(): string {
547
632
  return path.join(getAgentDir(), "extensions", CONTRARIAN_CONFIG_FILE);
548
633
  }
@@ -631,6 +716,7 @@ function formatUsage(stats: UsageStats): string {
631
716
  if (stats.output) parts.push(`↓${formatTokens(stats.output)}`);
632
717
  if (stats.cacheRead) parts.push(`R${formatTokens(stats.cacheRead)}`);
633
718
  if (stats.cacheWrite) parts.push(`W${formatTokens(stats.cacheWrite)}`);
719
+ if (stats.reasoning) parts.push(`T${formatTokens(stats.reasoning)}`);
634
720
  if (stats.cost) parts.push(`$${stats.cost.toFixed(4)}`);
635
721
  if (stats.contextTokens) parts.push(`ctx:${formatTokens(stats.contextTokens)}`);
636
722
  return parts.join(" ");
@@ -1034,6 +1120,7 @@ async function runContrarian(
1034
1120
  signal: AbortSignal | undefined,
1035
1121
  onUpdate: ((result: { content: Array<{ type: "text"; text: string }>; details: ContrarianDetails }) => void) | undefined,
1036
1122
  defaultCwd: string,
1123
+ spawnImpl: typeof spawn = spawn,
1037
1124
  ): Promise<{ ok: true; output: string; details: ContrarianDetails } | { ok: false; error: string; details: ContrarianDetails }> {
1038
1125
  const cwd = params.cwd ?? defaultCwd;
1039
1126
  const includeBash = params.includeBash ?? false;
@@ -1085,7 +1172,7 @@ async function runContrarian(
1085
1172
  let wasAborted = false;
1086
1173
 
1087
1174
  const exitCode = await new Promise<number>((resolve) => {
1088
- const proc = spawn(invocation.command, invocation.args, {
1175
+ const proc = spawnImpl(invocation.command, invocation.args, {
1089
1176
  cwd,
1090
1177
  shell: false,
1091
1178
  stdio: ["ignore", "pipe", "pipe"],
@@ -1123,18 +1210,16 @@ async function runContrarian(
1123
1210
  const errorMessageField = event.message.errorMessage;
1124
1211
  lastErrorMessage = typeof errorMessageField === "string" ? errorMessageField : undefined;
1125
1212
 
1126
- const messageUsage = event.message.usage;
1127
- if (messageUsage) {
1128
- usage.turns += 1;
1129
- usage.input += messageUsage.input || 0;
1130
- usage.output += messageUsage.output || 0;
1131
- usage.cacheRead += messageUsage.cacheRead || 0;
1132
- usage.cacheWrite += messageUsage.cacheWrite || 0;
1133
- usage.cost += messageUsage.cost?.total || 0;
1134
- usage.contextTokens = messageUsage.totalTokens || usage.contextTokens;
1135
- }
1136
-
1213
+ addUsageStats(usage, event.message.usage as Usage | undefined, {
1214
+ countTurn: true,
1215
+ updateContextTokens: true,
1216
+ });
1137
1217
  emit();
1218
+ return;
1219
+ }
1220
+
1221
+ if (event.type === "compaction_end") {
1222
+ addUsageStats(usage, event.result?.usage as Usage | undefined);
1138
1223
  }
1139
1224
  };
1140
1225
 
@@ -1150,26 +1235,53 @@ async function runContrarian(
1150
1235
  emit();
1151
1236
  });
1152
1237
 
1238
+ let closed = false;
1239
+ let killTimer: ReturnType<typeof setTimeout> | undefined;
1240
+ let abortListener: (() => void) | undefined;
1241
+
1242
+ const childHasExited = () => proc.exitCode !== null || proc.signalCode !== null;
1243
+ const clearEscalation = () => {
1244
+ if (killTimer) {
1245
+ clearTimeout(killTimer);
1246
+ killTimer = undefined;
1247
+ }
1248
+ };
1249
+ const cleanup = () => {
1250
+ clearEscalation();
1251
+ if (signal && abortListener) {
1252
+ signal.removeEventListener("abort", abortListener);
1253
+ abortListener = undefined;
1254
+ }
1255
+ };
1256
+
1257
+ proc.on("exit", () => {
1258
+ clearEscalation();
1259
+ });
1260
+
1153
1261
  proc.on("close", (code) => {
1262
+ closed = true;
1263
+ cleanup();
1154
1264
  if (buffer.trim()) processLine(buffer);
1155
1265
  resolve(code ?? 0);
1156
1266
  });
1157
1267
 
1158
1268
  proc.on("error", (error) => {
1269
+ cleanup();
1159
1270
  stderr += `${error instanceof Error ? error.message : String(error)}\n`;
1160
1271
  resolve(1);
1161
1272
  });
1162
1273
 
1163
1274
  if (signal) {
1164
- const killProc = () => {
1275
+ abortListener = () => {
1165
1276
  wasAborted = true;
1166
1277
  proc.kill("SIGTERM");
1167
- setTimeout(() => {
1168
- if (!proc.killed) proc.kill("SIGKILL");
1278
+ if (closed || childHasExited()) return;
1279
+ killTimer = setTimeout(() => {
1280
+ if (!closed && !childHasExited()) proc.kill("SIGKILL");
1169
1281
  }, 5000);
1170
1282
  };
1171
- if (signal.aborted) killProc();
1172
- else signal.addEventListener("abort", killProc, { once: true });
1283
+ if (signal.aborted) abortListener();
1284
+ else signal.addEventListener("abort", abortListener, { once: true });
1173
1285
  }
1174
1286
  });
1175
1287
 
@@ -1178,6 +1290,7 @@ async function runContrarian(
1178
1290
  details.durationMs = Date.now() - startedAt;
1179
1291
 
1180
1292
  if (wasAborted) {
1293
+ if (details.exitCode === 0) details.exitCode = 1;
1181
1294
  return { ok: false, error: "Contrarian was aborted.", details };
1182
1295
  }
1183
1296
 
@@ -1187,6 +1300,7 @@ async function runContrarian(
1187
1300
  // so callers can distinguish transient provider errors from a genuinely
1188
1301
  // empty response.
1189
1302
  if (lastStopReason === "error" || lastStopReason === "aborted") {
1303
+ if (details.exitCode === 0) details.exitCode = 1;
1190
1304
  return {
1191
1305
  ok: false,
1192
1306
  error: formatContrarianModelError(lastStopReason, lastErrorMessage),
@@ -1203,6 +1317,7 @@ async function runContrarian(
1203
1317
  }
1204
1318
 
1205
1319
  if (!finalOutput.trim()) {
1320
+ if (details.exitCode === 0) details.exitCode = 1;
1206
1321
  return {
1207
1322
  ok: false,
1208
1323
  error: details.stderr || "Contrarian finished without returning any text.",
@@ -1223,15 +1338,26 @@ function renderCollapsedText(text: string, lineLimit = COLLAPSED_LINE_LIMIT): st
1223
1338
  return [...lines.slice(0, lineLimit), `... (${lines.length - lineLimit} more lines)`].join("\n");
1224
1339
  }
1225
1340
 
1341
+ type ContrarianExtensionDeps = {
1342
+ spawnImpl?: typeof spawn;
1343
+ };
1344
+
1226
1345
  export const __test__ = {
1346
+ createContrarianExtension,
1227
1347
  findAvailableModel,
1228
1348
  isModelAvailabilityError,
1229
1349
  parseModelPreference,
1350
+ runContrarian,
1230
1351
  resolveThinkingLevel,
1231
1352
  selectContrarianModel,
1232
1353
  };
1233
1354
 
1234
1355
  export default function contrarianExtension(pi: ExtensionAPI) {
1356
+ createContrarianExtension(pi);
1357
+ }
1358
+
1359
+ function createContrarianExtension(pi: ExtensionAPI, deps: ContrarianExtensionDeps = {}) {
1360
+ const spawnImpl = deps.spawnImpl ?? spawn;
1235
1361
  const activeRuns = new Map<string, ContrarianUiRun>();
1236
1362
  let preferences: ContrarianPreferences = {};
1237
1363
 
@@ -1377,6 +1503,13 @@ export default function contrarianExtension(pi: ExtensionAPI) {
1377
1503
  },
1378
1504
  });
1379
1505
 
1506
+ pi.on("tool_result", async (event) => {
1507
+ if (event.toolName !== "contrarian") return undefined;
1508
+ const details = event.details as ContrarianDetails | undefined;
1509
+ if ((details?.exitCode ?? 0) === 0) return undefined;
1510
+ return { isError: true };
1511
+ });
1512
+
1380
1513
  pi.registerTool({
1381
1514
  name: "contrarian",
1382
1515
  label: "Contrarian",
@@ -1387,9 +1520,10 @@ export default function contrarianExtension(pi: ExtensionAPI) {
1387
1520
  promptGuidelines: [
1388
1521
  "Use contrarian when you need to stress-test a proposal, implementation plan, design, assumption, bug hypothesis, review conclusion, or product direction.",
1389
1522
  "Ask contrarian to steelman the strongest opposing case and separate confirmed objections, plausible concerns, and unresolved unknowns.",
1390
- "Do not use it for routine low-value work; it is slower than the main agent.",
1391
- "The contrarian is read-only by default. Set includeBash only when shell-based inspection is genuinely useful.",
1392
- "The contrarian requests high by default for reasoning models; defaults and explicit thinkingLevel overrides are clamped to the effective model-supported level when the model is matched.",
1523
+ "Do not use contrarian for routine low-value work; contrarian is slower than the main agent.",
1524
+ "The contrarian tool is read-only by default and only exposes read, grep, find, and ls unless contrarian includeBash is enabled.",
1525
+ "Set contrarian includeBash only when the extra bash inspection tool is genuinely useful; keep contrarian relying on read, grep, find, and ls otherwise.",
1526
+ "The contrarian tool requests high by default for reasoning models; contrarian defaults and explicit thinkingLevel overrides are clamped to the effective model-supported level when the model is matched.",
1393
1527
  ],
1394
1528
  parameters: ContrarianParams,
1395
1529
 
@@ -1414,6 +1548,14 @@ export default function contrarianExtension(pi: ExtensionAPI) {
1414
1548
  try {
1415
1549
  const attempts: ContrarianSelection[] = [];
1416
1550
  const seen = new Set<string>();
1551
+ const aggregateUsage = createEmptyUsage();
1552
+ const finalizeResult = (
1553
+ result: { content: Array<{ type: "text"; text: string }>; details: ContrarianDetails },
1554
+ ): AgentToolResult<ContrarianDetails> => ({
1555
+ ...result,
1556
+ details: { ...result.details, usage: cloneUsageStats(aggregateUsage) },
1557
+ usage: usageStatsToToolUsage(aggregateUsage),
1558
+ });
1417
1559
  const pushAttempt = (candidate: ContrarianSelection | undefined) => {
1418
1560
  if (!candidate) return;
1419
1561
  const key = `${candidate.provider}/${candidate.modelId}`.toLowerCase();
@@ -1455,7 +1597,7 @@ export default function contrarianExtension(pi: ExtensionAPI) {
1455
1597
  } else {
1456
1598
  const selectionResult = await selectContrarianModel(ctx, configuredThinkingLevel);
1457
1599
  if (!selectionResult.ok) {
1458
- return {
1600
+ return finalizeResult({
1459
1601
  content: [{ type: "text", text: selectionResult.error }],
1460
1602
  details: {
1461
1603
  modelRef: "",
@@ -1472,7 +1614,7 @@ export default function contrarianExtension(pi: ExtensionAPI) {
1472
1614
  durationMs: 0,
1473
1615
  cwd: params.cwd ?? ctx.cwd,
1474
1616
  },
1475
- };
1617
+ });
1476
1618
  }
1477
1619
  for (const candidate of selectionResult.ordered) pushAttempt(candidate);
1478
1620
  }
@@ -1496,25 +1638,26 @@ export default function contrarianExtension(pi: ExtensionAPI) {
1496
1638
  uiRun.selection = attempt;
1497
1639
  updateContrarianUi(ctx, activeRuns);
1498
1640
 
1499
- const result = await runContrarian(attempt, { ...params, task }, signal, handleUpdate, ctx.cwd);
1641
+ const result = await runContrarian(attempt, { ...params, task }, signal, handleUpdate, ctx.cwd, spawnImpl);
1642
+ mergeUsageStats(aggregateUsage, result.details.usage);
1500
1643
  if (result.ok) {
1501
- return {
1644
+ return finalizeResult({
1502
1645
  content: [{ type: "text", text: result.output }],
1503
1646
  details: result.details,
1504
- };
1647
+ });
1505
1648
  }
1506
1649
 
1507
1650
  lastErrorResult = result;
1508
1651
  const canFallBack = index < attempts.length - 1 && isModelAvailabilityError(result.error);
1509
1652
  if (!canFallBack) {
1510
- return {
1653
+ return finalizeResult({
1511
1654
  content: [{ type: "text", text: result.error }],
1512
1655
  details: result.details,
1513
- };
1656
+ });
1514
1657
  }
1515
1658
  }
1516
1659
 
1517
- return {
1660
+ return finalizeResult({
1518
1661
  content: [
1519
1662
  { type: "text", text: lastErrorResult?.error ?? "Contrarian could not select an available model." },
1520
1663
  ],
@@ -1534,7 +1677,7 @@ export default function contrarianExtension(pi: ExtensionAPI) {
1534
1677
  durationMs: 0,
1535
1678
  cwd: params.cwd ?? ctx.cwd,
1536
1679
  },
1537
- };
1680
+ });
1538
1681
  } finally {
1539
1682
  activeRuns.delete(toolCallId);
1540
1683
  updateContrarianUi(ctx, activeRuns);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-contrarian",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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.80.6
1
+ 0.82.0
@@ -2,7 +2,7 @@
2
2
 
3
3
  A small pi extension that prompts before session changes when the current git repo has uncommitted changes.
4
4
 
5
- This is copied from the original `dirty-repo-guard.ts` example in [`earendil-works/pi-mono`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/examples/extensions/dirty-repo-guard.ts) and kept basically the same.
5
+ This is copied from the original `dirty-repo-guard.ts` example in [`earendil-works/pi`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/dirty-repo-guard.ts) and kept basically the same.
6
6
 
7
7
  ## What it checks
8
8
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-dirty-repo-guard",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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.80.6
1
+ 0.82.0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-dynamic-context-pruning",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "A pi extension that dynamically prunes stale context to keep sessions lean.",
6
6
  "keywords": [
@@ -1 +1 @@
1
- 0.80.6
1
+ 0.82.0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-git-footer",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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.80.6
1
+ 0.82.0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-gnosis",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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.80.6
1
+ 0.82.0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-illustrations-to-explain-things",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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.80.6
1
+ 0.82.0
@@ -2,7 +2,7 @@
2
2
 
3
3
  A pi extension that expands inline bash commands in user prompts before they are sent to the agent.
4
4
 
5
- This started from the original `inline-bash.ts` example in [`earendil-works/pi-mono`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/examples/extensions/inline-bash.ts), with small packaging and safety tweaks.
5
+ This started from the original `inline-bash.ts` example in [`earendil-works/pi`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/inline-bash.ts), with small packaging and safety tweaks.
6
6
 
7
7
  ## Usage
8
8
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-inline-bash",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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.80.6
1
+ 0.82.0