@diegopetrucci/pi-extensions 0.1.76 → 0.1.79

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 (63) hide show
  1. package/README.md +8 -8
  2. package/extensions/agent-workflow-audit/README.md +1 -1
  3. package/extensions/agent-workflow-audit/index.ts +275 -59
  4. package/extensions/agent-workflow-audit/package.json +1 -1
  5. package/extensions/annotate-git-diff/git.ts +0 -8
  6. package/extensions/annotate-git-diff/glimpseui.d.ts +5 -65
  7. package/extensions/annotate-git-diff/package.json +1 -1
  8. package/extensions/annotate-last-message/glimpseui.d.ts +5 -65
  9. package/extensions/annotate-last-message/package.json +1 -1
  10. package/extensions/annotate-last-message/web/index.html +3 -30
  11. package/extensions/code-reviewer/README.md +5 -5
  12. package/extensions/code-reviewer/index.ts +252 -82
  13. package/extensions/code-reviewer/package.json +1 -1
  14. package/extensions/confirm-destructive/index.ts +2 -8
  15. package/extensions/confirm-destructive/package.json +1 -1
  16. package/extensions/context-cap/README.md +9 -9
  17. package/extensions/context-cap/index.ts +3 -3
  18. package/extensions/context-cap/package.json +2 -2
  19. package/extensions/context-inspector/index.ts +2 -46
  20. package/extensions/context-inspector/package.json +1 -1
  21. package/extensions/contrarian/README.md +4 -3
  22. package/extensions/contrarian/index.ts +48 -67
  23. package/extensions/contrarian/package.json +1 -1
  24. package/extensions/dirty-repo-guard/index.ts +0 -4
  25. package/extensions/dirty-repo-guard/package.json +1 -1
  26. package/extensions/dynamic-context-pruning/README.md +20 -11
  27. package/extensions/dynamic-context-pruning/index.ts +118 -341
  28. package/extensions/dynamic-context-pruning/package.json +1 -1
  29. package/extensions/fast/README.md +18 -11
  30. package/extensions/fast/index.ts +9 -5
  31. package/extensions/fast/package.json +1 -1
  32. package/extensions/git-footer/index.ts +2 -16
  33. package/extensions/git-footer/package.json +1 -1
  34. package/extensions/inline-bash/index.ts +0 -16
  35. package/extensions/inline-bash/package.json +1 -1
  36. package/extensions/librarian/README.md +1 -1
  37. package/extensions/librarian/index.ts +246 -70
  38. package/extensions/librarian/package.json +1 -1
  39. package/extensions/minimal-footer/README.md +5 -5
  40. package/extensions/minimal-footer/index.ts +5 -26
  41. package/extensions/minimal-footer/minimal-footer.example.json +1 -1
  42. package/extensions/minimal-footer/openai-usage.ts +2 -11
  43. package/extensions/minimal-footer/package.json +1 -1
  44. package/extensions/oracle/README.md +4 -3
  45. package/extensions/oracle/index.ts +46 -65
  46. package/extensions/oracle/package.json +1 -1
  47. package/extensions/permission-gate/README.md +3 -0
  48. package/extensions/permission-gate/index.ts +127 -36
  49. package/extensions/permission-gate/package.json +1 -1
  50. package/extensions/project-mcp-json/README.md +2 -2
  51. package/extensions/project-mcp-json/package.json +1 -1
  52. package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
  53. package/extensions/quiet-tools/README.md +30 -19
  54. package/extensions/quiet-tools/index.ts +233 -166
  55. package/extensions/quiet-tools/package.json +2 -2
  56. package/extensions/review/README.md +2 -0
  57. package/extensions/review/package.json +1 -1
  58. package/extensions/todo/index.ts +0 -19
  59. package/extensions/todo/package.json +1 -1
  60. package/extensions/triage-comments/README.md +4 -2
  61. package/extensions/triage-comments/index.ts +344 -39
  62. package/extensions/triage-comments/package.json +1 -1
  63. package/package.json +4 -4
@@ -143,7 +143,6 @@ type MinimalMessage = {
143
143
  customType?: string;
144
144
  display?: boolean;
145
145
  summary?: string;
146
- fromId?: string;
147
146
  tokensBefore?: number;
148
147
  command?: string;
149
148
  output?: string;
@@ -165,7 +164,6 @@ type MinimalEntry = {
165
164
  display?: boolean;
166
165
  details?: unknown;
167
166
  summary?: string;
168
- fromId?: string;
169
167
  tokensBefore?: number;
170
168
  firstKeptEntryId?: string;
171
169
  };
@@ -177,7 +175,6 @@ type MinimalProjectionEntry = {
177
175
 
178
176
  type MinimalSessionProjection = {
179
177
  entries: MinimalProjectionEntry[];
180
- messages?: MinimalMessage[];
181
178
  };
182
179
 
183
180
  type AnalysisResult = {
@@ -192,7 +189,6 @@ type Segment = {
192
189
  source: string;
193
190
  role: string;
194
191
  tokens: number;
195
- chars: number;
196
192
  turn: number;
197
193
  sequence: number;
198
194
  entryId?: string;
@@ -211,7 +207,6 @@ type Segment = {
211
207
  type CategoryStat = {
212
208
  id: CategoryId;
213
209
  label: string;
214
- shortLabel: string;
215
210
  color: string;
216
211
  description: string;
217
212
  tokens: number;
@@ -230,13 +225,9 @@ type AggregateStat = {
230
225
 
231
226
  type DatasetStats = {
232
227
  tokens: number;
233
- rawTokens: number;
234
- visibleRawTokens: number;
235
228
  excludedTokens: number;
236
229
  providerDeltaTokens: number;
237
230
  estimatorOverageTokens: number;
238
- reconciliationScale: number;
239
- segmentCount: number;
240
231
  messageCount: number;
241
232
  categories: CategoryStat[];
242
233
  topSegments: Segment[];
@@ -247,8 +238,6 @@ type DatasetStats = {
247
238
 
248
239
  type Dataset = {
249
240
  id: DatasetId;
250
- label: string;
251
- description: string;
252
241
  segments: Segment[];
253
242
  stats: DatasetStats;
254
243
  };
@@ -259,7 +248,6 @@ type RoutedModel = {
259
248
  };
260
249
 
261
250
  type ReportData = {
262
- generatedAt: string;
263
251
  cwd: string;
264
252
  session: {
265
253
  id?: string;
@@ -280,7 +268,6 @@ type ReportData = {
280
268
  percent: number | null;
281
269
  };
282
270
  options: {
283
- redacted: boolean;
284
271
  defaultDataset: DatasetId;
285
272
  };
286
273
  categoryMeta: typeof CATEGORY_META;
@@ -507,7 +494,6 @@ function addTextSegment(
507
494
  source: base.source,
508
495
  role: base.role,
509
496
  tokens,
510
- chars,
511
497
  turn: base.turn ?? state.turn,
512
498
  entryId: base.entryId,
513
499
  timestamp: base.timestamp,
@@ -543,7 +529,6 @@ function addImageSegment(
543
529
  source: base.source,
544
530
  role: base.role,
545
531
  tokens: IMAGE_TOKEN_ESTIMATE,
546
- chars: IMAGE_TOKEN_ESTIMATE * 4,
547
532
  turn: base.turn ?? state.turn,
548
533
  entryId: base.entryId,
549
534
  timestamp: base.timestamp,
@@ -820,7 +805,6 @@ function contextEntryToMessage(entry: MinimalEntry): MinimalMessage | undefined
820
805
  return {
821
806
  role: "branchSummary",
822
807
  summary: entry.summary,
823
- fromId: entry.fromId,
824
808
  timestamp: entry.timestamp ? new Date(entry.timestamp).getTime() : undefined,
825
809
  };
826
810
  }
@@ -916,10 +900,7 @@ function resolveSessionProjection(
916
900
  (!Array.isArray(projection.messages) || !projection.messages.every(isProjectionMessage))
917
901
  )
918
902
  return undefined;
919
- return {
920
- entries,
921
- messages: entries.flatMap((entry) => entry.messages),
922
- };
903
+ return { entries };
923
904
  }
924
905
 
925
906
  function resolveCurrentContextEntries(
@@ -1045,8 +1026,6 @@ function aggregateBy(
1045
1026
 
1046
1027
  function finalizeDataset(
1047
1028
  id: DatasetId,
1048
- label: string,
1049
- description: string,
1050
1029
  segments: Segment[],
1051
1030
  messageCount: number,
1052
1031
  authoritativeTokens: number | null,
@@ -1091,7 +1070,6 @@ function finalizeDataset(
1091
1070
  return {
1092
1071
  id: categoryId,
1093
1072
  label: meta.label,
1094
- shortLabel: meta.shortLabel,
1095
1073
  color: meta.color,
1096
1074
  description: meta.description,
1097
1075
  tokens: Math.round(rawTokens),
@@ -1105,18 +1083,12 @@ function finalizeDataset(
1105
1083
 
1106
1084
  return {
1107
1085
  id,
1108
- label,
1109
- description,
1110
1086
  segments,
1111
1087
  stats: {
1112
1088
  tokens: Math.round(displayTotal),
1113
- rawTokens: Math.round(segments.reduce((sum, segment) => sum + segment.tokens, 0)),
1114
- visibleRawTokens: Math.round(visibleRawTokens),
1115
1089
  excludedTokens: Math.round(excludedTokens),
1116
1090
  providerDeltaTokens,
1117
1091
  estimatorOverageTokens,
1118
- reconciliationScale,
1119
- segmentCount: segments.length,
1120
1092
  messageCount,
1121
1093
  categories,
1122
1094
  topSegments: [...visibleSegments].sort((a, b) => b.tokens - a.tokens).slice(0, 24),
@@ -1192,20 +1164,11 @@ function buildReportData(
1192
1164
  const fullSegments = [...overhead, ...fullAnalysis.segments];
1193
1165
  const currentDataset = finalizeDataset(
1194
1166
  "current",
1195
- "Current model context",
1196
- "What pi is expected to send to the model on the next turn: latest compaction summary plus unsummarized messages, system prompt, and active tool schemas.",
1197
1167
  currentSegments,
1198
1168
  currentAnalysis.messageCount,
1199
1169
  contextTokens,
1200
1170
  );
1201
- const fullDataset = finalizeDataset(
1202
- "full",
1203
- "Full active branch history",
1204
- "Every context-bearing entry on the active branch, including pre-compaction history that may no longer be sent verbatim.",
1205
- fullSegments,
1206
- fullAnalysis.messageCount,
1207
- null,
1208
- );
1171
+ const fullDataset = finalizeDataset("full", fullSegments, fullAnalysis.messageCount, null);
1209
1172
 
1210
1173
  const notes: string[] = [
1211
1174
  "Per-component token counts are local estimates based mostly on characters/4. Providers expose aggregate usage, not exact per-message attribution.",
@@ -1250,7 +1213,6 @@ function buildReportData(
1250
1213
  }
1251
1214
 
1252
1215
  return {
1253
- generatedAt: new Date().toISOString(),
1254
1216
  cwd: options.redact ? "[redacted cwd]" : ctx.cwd,
1255
1217
  session: {
1256
1218
  id: options.redact ? "[redacted session]" : safeCall(() => ctx.sessionManager.getSessionId()),
@@ -1273,7 +1235,6 @@ function buildReportData(
1273
1235
  percent: contextPercent,
1274
1236
  },
1275
1237
  options: {
1276
- redacted: options.redact,
1277
1238
  defaultDataset: options.defaultDataset,
1278
1239
  },
1279
1240
  categoryMeta: CATEGORY_META,
@@ -1368,16 +1329,12 @@ function buildHtml(data: ReportData): string {
1368
1329
  color-scheme: dark;
1369
1330
  --bg: #070a12;
1370
1331
  --panel: rgba(15, 23, 42, 0.72);
1371
- --panel-strong: rgba(15, 23, 42, 0.94);
1372
1332
  --line: rgba(148, 163, 184, 0.18);
1373
1333
  --text: #e5edf7;
1374
1334
  --muted: #94a3b8;
1375
1335
  --dim: #64748b;
1376
1336
  --accent: #8b5cf6;
1377
1337
  --accent-2: #06b6d4;
1378
- --good: #22c55e;
1379
- --warn: #f59e0b;
1380
- --bad: #ef4444;
1381
1338
  --shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
1382
1339
  }
1383
1340
  * { box-sizing: border-box; }
@@ -1588,7 +1545,6 @@ pre { margin: 0; white-space: pre-wrap; word-break: break-word; color: #cbd5e1;
1588
1545
  for (const [key, value] of Object.entries(attrs)) {
1589
1546
  if (key === 'class') node.className = value;
1590
1547
  else if (key === 'text') node.textContent = value;
1591
- else if (key === 'html') node.innerHTML = value;
1592
1548
  else if (key.startsWith('on') && typeof value === 'function') node.addEventListener(key.slice(2), value);
1593
1549
  else if (value !== undefined && value !== null) node.setAttribute(key, value);
1594
1550
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-context-inspector",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
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,6 +1,6 @@
1
1
  # contrarian
2
2
 
3
- A read-only contrarian subagent for [pi](https://github.com/earendil-works/pi).
3
+ A contrarian subagent with read-only built-in tools and configured MCP tools for [pi](https://github.com/earendil-works/pi).
4
4
 
5
5
  It adds a `contrarian` tool that spins up a separate pi subprocess to stress-test plans, designs, assumptions, bug hypotheses, review conclusions, and product directions by steelmanning the strongest credible opposing case.
6
6
 
@@ -8,7 +8,7 @@ This is adapted from the TLH [`contrarian` subagent prompt](https://github.com/d
8
8
 
9
9
  ## What it does
10
10
 
11
- - creates an isolated read-only subprocess
11
+ - creates an isolated subprocess with read-only built-in tools (read, grep, find, ls) and any configured MCP tools
12
12
  - prefers a strong model on the opposite model family/provider when available, then falls back to the best available model
13
13
  - requests `high` reasoning by default for reasoning models, then clamps to the model-supported thinking level
14
14
  - defaults to `read,grep,find,ls`, with optional non-mutating bash inspection
@@ -81,7 +81,8 @@ When the current Pi session has a non-empty model scope, Contrarian limits autom
81
81
 
82
82
  ## Notes
83
83
 
84
- - The contrarian is intentionally **read-only**.
84
+ - The contrarian subprocess uses read-only built-in tools (read, grep, find, ls) by default, plus any configured MCP tools. It can optionally include bash for non-mutating inspection.
85
+ - Configured MCP tools run without confirmation inside the subprocess and may have side effects — use them only for gathering evidence. MCP access requires host Pi >=1.0.4; older hosts degrade to no MCP, no error.
85
86
  - It never implements fixes or produces patches.
86
87
  - Its output separates confirmed objections, plausible concerns, and unresolved unknowns.
87
88
  - It is best for adversarial review before committing to a plan, design, or conclusion.
@@ -102,8 +102,15 @@ interface ContrarianPreferences {
102
102
  thinkingLevel?: ThinkingLevel;
103
103
  }
104
104
 
105
- const READ_ONLY_TOOLS = ["read", "grep", "find", "ls"];
106
- const READ_ONLY_PLUS_BASH_TOOLS = [...READ_ONLY_TOOLS, "bash"];
105
+ const SUBAGENT_MCP_TOOLS = [
106
+ "mcp__*",
107
+ "tool_search",
108
+ "list_mcp_resources",
109
+ "list_mcp_resource_templates",
110
+ "read_mcp_resource",
111
+ ];
112
+ const READ_ONLY_TOOLS = ["read", "grep", "find", "ls", ...SUBAGENT_MCP_TOOLS];
113
+ const READ_ONLY_PLUS_BASH_TOOLS = ["read", "grep", "find", "ls", "bash", ...SUBAGENT_MCP_TOOLS];
107
114
  const DEFAULT_THINKING_LEVEL: ThinkingLevel = "high";
108
115
  const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
109
116
  const COLLAPSED_LINE_LIMIT = 8;
@@ -161,6 +168,33 @@ const CONTRARIAN_MODEL_PREFERENCES = [
161
168
  "gemini-3.5-flash",
162
169
  ];
163
170
 
171
+ const AZURE_MODEL_PREFERENCES = [
172
+ "gpt-6-astra ",
173
+ "gpt-6-astra",
174
+ "gpt-6.1-sol ",
175
+ "gpt-6-sol ",
176
+ "gpt-6-sol",
177
+ "gpt-5.6-sol",
178
+ "gpt-5.6-terra",
179
+ "gpt-6-luna ",
180
+ "gpt-6-luna",
181
+ "gpt-5.6-luna",
182
+ "gpt-5.5-pro",
183
+ "gpt-5.5",
184
+ "gpt-5.4-pro",
185
+ "gpt-5.4 ",
186
+ "gpt-5.3-codex",
187
+ "gpt-5-pro",
188
+ "gpt-5-chat-latest",
189
+ "gpt-5.2-pro",
190
+ "gpt-5.2",
191
+ "gpt-5.1",
192
+ "o3-pro",
193
+ "o1-pro",
194
+ "gpt-5.4-mini",
195
+ "gpt-5-mini",
196
+ ];
197
+
164
198
  const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
165
199
  "amazon-bedrock": [
166
200
  "global.anthropic.claude-opus-5-5 ",
@@ -213,32 +247,9 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
213
247
  "claude-sonnet-4-5",
214
248
  ],
215
249
  "ant-ling": ["Ling-2.6-1T", "Ling-2.6-flash"],
216
- "azure-openai-responses": [
217
- "gpt-6-astra ",
218
- "gpt-6-astra",
219
- "gpt-6.1-sol ",
220
- "gpt-6-sol ",
221
- "gpt-6-sol",
222
- "gpt-5.6-sol",
223
- "gpt-5.6-terra",
224
- "gpt-6-luna ",
225
- "gpt-6-luna",
226
- "gpt-5.6-luna",
227
- "gpt-5.5-pro",
228
- "gpt-5.5",
229
- "gpt-5.4-pro",
230
- "gpt-5.4 ",
231
- "gpt-5.3-codex",
232
- "gpt-5-pro",
233
- "gpt-5-chat-latest",
234
- "gpt-5.2-pro",
235
- "gpt-5.2",
236
- "gpt-5.1",
237
- "o3-pro",
238
- "o1-pro",
239
- "gpt-5.4-mini",
240
- "gpt-5-mini",
241
- ],
250
+ azure: AZURE_MODEL_PREFERENCES,
251
+ // Legacy alias for Pi 1.0.0–1.0.2 hosts (renamed to `azure` in Pi 1.0.3).
252
+ "azure-openai-responses": AZURE_MODEL_PREFERENCES,
242
253
  baseten: [
243
254
  "moonshotai/Kimi-K3",
244
255
  "moonshotai/Kimi-K2.7-Code ",
@@ -263,10 +274,6 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
263
274
  "cloudflare-ai-gateway": [
264
275
  "claude-fable-5",
265
276
  "claude-opus-5",
266
- "claude-opus-4.8",
267
- "claude-opus-4.7",
268
- "claude-opus-4.6",
269
- "claude-opus-4.5",
270
277
  "gpt-6-astra ",
271
278
  "gpt-6-astra",
272
279
  "gpt-5.5",
@@ -603,9 +610,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
603
610
  "MiniMaxAI/MiniMax-M3",
604
611
  "MiniMaxAI/MiniMax-M2.7",
605
612
  "openai/gpt-oss-120b",
606
- "openai/gpt-oss-20b",
607
613
  "nvidia/nemotron-3-ultra-550b-a55b",
608
- "google/gemma-4-31B-it",
609
614
  ],
610
615
  typesafe: [],
611
616
  "vercel-ai-gateway": [
@@ -681,13 +686,14 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
681
686
  };
682
687
 
683
688
  const CONTRARIAN_SYSTEM_PROMPT = [
684
- "You are the Contrarian: an independent, read-only adversarial analyst.",
689
+ "You are the Contrarian: an independent adversarial analyst with access to read-only built-in tools and any configured MCP tools.",
685
690
  "Your job is to stress-test a proposal, plan, design, assumption, bug hypothesis, review conclusion, product direction, or decision by developing the strongest credible opposing case for the delegating primary agent.",
686
- "You are read-only. Never modify files, create patches, install dependencies, change configuration, implement fixes, or delegate work to other agents. Your output is adversarial analysis, evidence, and recommendations only.",
691
+ "Never modify files, create patches, install dependencies, change configuration, implement fixes, or delegate work to other agents. Your output is adversarial analysis, evidence, and recommendations only.",
687
692
  "Use the available tools to inspect the repository and gather evidence. If bash is available, use it only for non-mutating inspection commands.",
693
+ "Configured MCP tools are available and run without confirmation inside this subagent. Use them only to gather evidence or context. Avoid side-effecting MCP actions unless the task explicitly asks for them.",
688
694
  "Analysis process:",
689
695
  "1. Identify the core claim, decision, or direction being challenged.",
690
- "2. Gather the local read-only context needed to evaluate it accurately.",
696
+ "2. Gather the local context needed to evaluate it accurately.",
691
697
  "3. Steelman the strongest credible opposing position before judging whether it holds.",
692
698
  "4. Identify hidden assumptions, failure modes, alternative interpretations, tradeoffs, and disconfirming evidence.",
693
699
  "5. Clearly separate confirmed objections, plausible concerns, and unresolved unknowns.",
@@ -1814,15 +1820,16 @@ function createContrarianExtension(pi: ExtensionAPI, deps: ContrarianExtensionDe
1814
1820
  name: "contrarian",
1815
1821
  label: "Contrarian",
1816
1822
  description:
1817
- "Consult a separate read-only contrarian subprocess that stress-tests plans, designs, assumptions, bug hypotheses, and conclusions by steelmanning the strongest opposing case.",
1823
+ "Consult a separate contrarian subprocess with read-only built-in tools and configured MCP tools that stress-tests plans, designs, assumptions, bug hypotheses, and conclusions by steelmanning the strongest opposing case.",
1818
1824
  promptSnippet:
1819
- "Consult a read-only contrarian that prefers a strong opposite-family/provider model for adversarial analysis.",
1825
+ "Consult a contrarian subprocess that prefers a strong opposite-family/provider model for adversarial analysis; exposes read-only built-in tools plus any configured MCP tools.",
1820
1826
  promptGuidelines: [
1821
1827
  "Use contrarian when you need to stress-test a proposal, implementation plan, design, assumption, bug hypothesis, review conclusion, or product direction.",
1822
1828
  "Ask contrarian to steelman the strongest opposing case and separate confirmed objections, plausible concerns, and unresolved unknowns.",
1823
1829
  "Do not use contrarian for routine low-value work; contrarian is slower than the main agent.",
1824
- "The contrarian tool is read-only by default and only exposes read, grep, find, and ls unless contrarian includeBash is enabled.",
1830
+ "The contrarian tool exposes read-only built-in tools (read, grep, find, and ls) plus any configured MCP tools; contrarian includeBash adds the bash inspection tool.",
1825
1831
  "Set contrarian includeBash only when the extra bash inspection tool is genuinely useful; keep contrarian relying on read, grep, find, and ls otherwise.",
1832
+ "Configured MCP tools are available to contrarian and run without confirmation inside the subagent; they may have side effects — use them only for gathering evidence, not for state-changing actions unless the task explicitly asks. MCP access requires host Pi >=1.0.4; older hosts degrade to no MCP, no error.",
1826
1833
  "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.",
1827
1834
  ],
1828
1835
  parameters: ContrarianParams,
@@ -1937,8 +1944,7 @@ function createContrarianExtension(pi: ExtensionAPI, deps: ContrarianExtensionDe
1937
1944
  onUpdate?.(partial);
1938
1945
  };
1939
1946
 
1940
- let lastErrorResult: { ok: false; error: string; details: ContrarianDetails } | undefined;
1941
- for (let index = 0; index < attempts.length; index++) {
1947
+ for (let index = 0; ; index++) {
1942
1948
  const previous = index > 0 ? attempts[index - 1] : undefined;
1943
1949
  const attempt = previous
1944
1950
  ? withFallbackReason(attempts[index], previous)
@@ -1962,7 +1968,6 @@ function createContrarianExtension(pi: ExtensionAPI, deps: ContrarianExtensionDe
1962
1968
  });
1963
1969
  }
1964
1970
 
1965
- lastErrorResult = result;
1966
1971
  const canFallBack = index < attempts.length - 1 && isModelAvailabilityError(result.error);
1967
1972
  if (!canFallBack) {
1968
1973
  return finalizeResult({
@@ -1971,30 +1976,6 @@ function createContrarianExtension(pi: ExtensionAPI, deps: ContrarianExtensionDe
1971
1976
  });
1972
1977
  }
1973
1978
  }
1974
-
1975
- return finalizeResult({
1976
- content: [
1977
- {
1978
- type: "text",
1979
- text: lastErrorResult?.error ?? "Contrarian could not select an available model.",
1980
- },
1981
- ],
1982
- details: lastErrorResult?.details ?? {
1983
- modelRef: "",
1984
- provider: ctx.model?.provider ?? "unknown",
1985
- modelId: "",
1986
- modelName: undefined,
1987
- thinkingLevel: configuredThinkingLevel ?? DEFAULT_THINKING_LEVEL,
1988
- autoSelected: true,
1989
- selectionReason: "No authenticated models were available to run the contrarian.",
1990
- includeBash: params.includeBash ?? false,
1991
- usage: createEmptyUsage(),
1992
- stderr: "",
1993
- exitCode: 1,
1994
- durationMs: 0,
1995
- cwd: params.cwd ?? ctx.cwd,
1996
- },
1997
- });
1998
1979
  } finally {
1999
1980
  activeRuns.delete(toolCallId);
2000
1981
  updateContrarianUi(ctx, activeRuns);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-contrarian",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
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",
@@ -12,11 +12,9 @@ async function checkDirtyRepo(
12
12
  ctx: ExtensionContext,
13
13
  action: string,
14
14
  ): Promise<{ cancel: boolean } | undefined> {
15
- // Check for uncommitted changes
16
15
  const { stdout, code } = await pi.exec("git", ["status", "--porcelain"]);
17
16
 
18
17
  if (code !== 0) {
19
- // Not a git repo, allow the action
20
18
  return;
21
19
  }
22
20
 
@@ -26,11 +24,9 @@ async function checkDirtyRepo(
26
24
  }
27
25
 
28
26
  if (!ctx.hasUI) {
29
- // In non-interactive mode, block by default
30
27
  return { cancel: true };
31
28
  }
32
29
 
33
- // Count changed files
34
30
  const changedFiles = stdout.trim().split("\n").filter(Boolean).length;
35
31
 
36
32
  const choice = await ctx.ui.select(
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-dirty-repo-guard",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
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",
@@ -256,9 +256,9 @@ Notes:
256
256
  survives this floor.
257
257
  - `gate.breakEvenThresholdByState` lets `idle` vs `mid_loop` agent states use
258
258
  different thresholds; `idle` defaults to `22` and
259
- `mid_loop` defaults to `1`. Real
260
- mid-loop/idle detection is wired through the `context` event handler
261
- (pe-zy4s): the state is classified straight from the message payload —
259
+ `mid_loop` defaults to `1`. Real mid-loop/idle detection is wired through
260
+ the `context` event handler: the state is classified straight from the
261
+ message payload —
262
262
  `idle` if the most recent relevant message is a user message (this is the
263
263
  first LLM call of the turn), `mid_loop` if it's an assistant message or a
264
264
  tool result (the agent is iterating mid-turn). The representative-corpus
@@ -278,8 +278,10 @@ Notes:
278
278
  - `gate.breakEvenThreshold`'s default of `22` is calibrated from the
279
279
  representative-corpus benchmark at `cachedPriceRatio` r=0.1 (aggressive
280
280
  prompt caching, the common case). It is **provider/ratio-
281
- dependent**: the optimal threshold rises as caching gets weaker (T=29 at
282
- r=0.25, T=54 at r=0.5, T=58 at r=0.9 on the same corpus).
281
+ dependent**. In the **historical, retired turn-end run**, the optimal
282
+ threshold rose as caching got weaker (T=29 at r=0.25, T=54 at r=0.5, T=58
283
+ at r=0.9); these are not current turn-start results. See
284
+ [`docs/v2-design.md` §4](./docs/v2-design.md#4-benchmark-decision-gate).
283
285
 
284
286
  ## Prompt-cache trade-off
285
287
 
@@ -326,6 +328,8 @@ Options:
326
328
  - `--limit N`: only process the first N session files found.
327
329
  - `--ratio R`: cached-price ratio(s) to model; repeatable and/or
328
330
  comma-separated (e.g. `--ratio 0.1,0.2`). Default: `0.1`.
331
+ - `--sweep-max N`: explicit ceiling for the auto-expanding threshold sweep
332
+ (overrides the corpus-derived ceiling). Default: auto-expand from 30 as needed.
329
333
  - `--json`: emit a full machine-readable JSON dump instead of aligned text.
330
334
  - `--simulate-compression`: additionally SIMULATE v2-style range compression
331
335
  (docs/v2-design.md §1/§4 go/no-go evidence) alongside — never mixed into —
@@ -390,13 +394,15 @@ reports two kinds of numbers:
390
394
  prune point, and the *actual* net benefit that prune would have produced —
391
395
  `realizedNetBenefit = actualRemainingCalls * recurringSaving - penalty`.
392
396
 
393
- It also sweeps the break-even threshold `T` from 1 to 30 and reports the `T`
394
- that maximizes total realized net benefit across all gate-eligible
395
- candidates, both overall and split by idle/mid-loop agent state.
397
+ It also sweeps the break-even threshold `T` from 1 to 30, then keeps doubling
398
+ the range while the best `T` sits at the top edge, up to a corpus-derived
399
+ ceiling (hard cap 500). `--sweep-max N` sets that ceiling explicitly. It
400
+ reports the `T` that maximizes total realized net benefit across all
401
+ gate-eligible candidates, both overall and split by idle/mid-loop agent state.
396
402
 
397
403
  ## Comparison
398
404
 
399
- - **[opencode-dynamic-context-pruning](https://github.com/sculptdotfun/opencode-dynamic-context-pruning) ("pi-dcp" upstream)**:
405
+ - **[opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) ("pi-dcp" upstream)**:
400
406
  the OpenCode plugin this extension takes its name and general problem space
401
407
  from. It ships a model-invoked `compress` tool (agentic, nested-summary
402
408
  compression), autonomous nudges, manual mode, and a full `/dcp` command
@@ -428,9 +434,11 @@ net benefit, at sufficient candidate volume, with enough remaining-calls
428
434
  runway per session to plausibly amortize a compression summary's own token
429
435
  cost — see `docs/v2-design.md` §4 for the exact go/no-go bar.
430
436
 
431
- The representative-corpus run of the benchmark harness (`pe-c5n9`, over
437
+ The **historical, retired turn-end run** of the benchmark harness (over
432
438
  `~/.the-last-harness/agent/sessions`: 1,390+ session files, 556 gate-eligible
433
- candidates — see the corpus-choice note above) produced:
439
+ candidates — see the corpus-choice note above) produced the following
440
+ figures, preserved as historical context rather than current turn-start
441
+ results (see [`docs/v2-design.md` §4](./docs/v2-design.md#4-benchmark-decision-gate)):
434
442
 
435
443
  - **Candidates and tokens removed by strategy**: `dedupe` 111 candidates /
436
444
  93,330 tokens removed; `error-purge` 187 / 28,191; `superseded-file-ops`
@@ -440,6 +448,7 @@ candidates — see the corpus-choice note above) produced:
440
448
  this corpus is dominated by long, tool-heavy agent sessions rather than
441
449
  short orchestrator sessions.
442
450
 
451
+ The current turn-start re-derivation has 460 gate-eligible candidates.
443
452
  Shipped gate defaults remain `idle=22` / `mid_loop=1` (see
444
453
  [Configuration](#configuration)).
445
454