@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.
- package/.pi-fleet-tested-version +1 -1
- package/extensions/agent-workflow-audit/.pi-fleet-tested-version +1 -1
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/.pi-fleet-tested-version +1 -1
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-last-message/.pi-fleet-tested-version +1 -1
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/brrr/.pi-fleet-tested-version +1 -1
- package/extensions/brrr/package.json +1 -1
- package/extensions/claude-fast/.pi-fleet-tested-version +1 -1
- package/extensions/claude-fast/package.json +1 -1
- package/extensions/code-reviewer/.pi-fleet-tested-version +1 -1
- package/extensions/code-reviewer/index.ts +120 -37
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/confirm-destructive/.pi-fleet-tested-version +1 -1
- package/extensions/confirm-destructive/README.md +1 -1
- package/extensions/confirm-destructive/index.ts +20 -5
- package/extensions/confirm-destructive/package.json +1 -1
- package/extensions/context-cap/.pi-fleet-tested-version +1 -1
- package/extensions/context-cap/package.json +1 -1
- package/extensions/context-inspector/.pi-fleet-tested-version +1 -1
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/.pi-fleet-tested-version +1 -1
- package/extensions/contrarian/README.md +1 -1
- package/extensions/contrarian/index.ts +175 -32
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/.pi-fleet-tested-version +1 -1
- package/extensions/dirty-repo-guard/README.md +1 -1
- package/extensions/dirty-repo-guard/package.json +1 -1
- package/extensions/dynamic-context-pruning/.pi-fleet-tested-version +1 -1
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/git-footer/.pi-fleet-tested-version +1 -1
- package/extensions/git-footer/package.json +1 -1
- package/extensions/gnosis/.pi-fleet-tested-version +1 -1
- package/extensions/gnosis/package.json +1 -1
- package/extensions/illustrations-to-explain-things/.pi-fleet-tested-version +1 -1
- package/extensions/illustrations-to-explain-things/package.json +1 -1
- package/extensions/inline-bash/.pi-fleet-tested-version +1 -1
- package/extensions/inline-bash/README.md +1 -1
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/.pi-fleet-tested-version +1 -1
- package/extensions/librarian/index.ts +385 -106
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/.pi-fleet-tested-version +1 -1
- package/extensions/minimal-footer/package.json +1 -1
- package/extensions/notify/.pi-fleet-tested-version +1 -1
- package/extensions/notify/README.md +1 -1
- package/extensions/notify/package.json +1 -1
- package/extensions/openai-fast/.pi-fleet-tested-version +1 -1
- package/extensions/openai-fast/package.json +1 -1
- package/extensions/oracle/.pi-fleet-tested-version +1 -1
- package/extensions/oracle/README.md +1 -1
- package/extensions/oracle/index.ts +176 -33
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/.pi-fleet-tested-version +1 -1
- package/extensions/permission-gate/README.md +1 -1
- package/extensions/permission-gate/index.ts +453 -2
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
- package/extensions/quiet-tools/package.json +1 -1
- package/extensions/review/.pi-fleet-tested-version +1 -1
- package/extensions/review/index.ts +53 -19
- package/extensions/review/package.json +1 -1
- package/extensions/todo/.pi-fleet-tested-version +1 -1
- package/extensions/todo/README.md +1 -1
- package/extensions/todo/package.json +1 -1
- package/extensions/triage-comments/.pi-fleet-tested-version +1 -1
- package/extensions/triage-comments/index.ts +107 -33
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +4 -4
package/.pi-fleet-tested-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.82.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.82.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.82.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-annotate-git-diff",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.82.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-annotate-last-message",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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.
|
|
1
|
+
0.82.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.82.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.82.0
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
} from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { Type } from "typebox";
|
|
13
13
|
|
|
14
|
+
import type { Usage } from "@earendil-works/pi-ai";
|
|
15
|
+
|
|
14
16
|
const MAX_TOOL_CALLS_TO_KEEP = 80;
|
|
15
17
|
const MAX_TURNS = 8;
|
|
16
18
|
const MAX_RUN_MS = 8 * 60 * 1000;
|
|
@@ -463,26 +465,72 @@ function applyModelAttemptDetails(details: ReviewDetails, model: PiModel, thinki
|
|
|
463
465
|
details.thinkingLevelNote = thinking.note;
|
|
464
466
|
}
|
|
465
467
|
|
|
466
|
-
function
|
|
468
|
+
function createEmptyUsage(): Usage {
|
|
469
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function addUsage(total: Usage, usage: Usage): void {
|
|
473
|
+
total.input += usage.input || 0;
|
|
474
|
+
total.output += usage.output || 0;
|
|
475
|
+
total.cacheRead += usage.cacheRead || 0;
|
|
476
|
+
total.cacheWrite += usage.cacheWrite || 0;
|
|
477
|
+
if (usage.cacheWrite1h !== undefined) total.cacheWrite1h = (total.cacheWrite1h ?? 0) + usage.cacheWrite1h;
|
|
478
|
+
if (usage.reasoning !== undefined) total.reasoning = (total.reasoning ?? 0) + usage.reasoning;
|
|
479
|
+
total.totalTokens += usage.totalTokens || 0;
|
|
480
|
+
total.cost.input += usage.cost?.input || 0;
|
|
481
|
+
total.cost.output += usage.cost?.output || 0;
|
|
482
|
+
total.cost.cacheRead += usage.cost?.cacheRead || 0;
|
|
483
|
+
total.cost.cacheWrite += usage.cost?.cacheWrite || 0;
|
|
484
|
+
total.cost.total += usage.cost?.total || 0;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
type AssistantLikeMessage = { role?: string; content?: unknown; stopReason?: unknown; errorMessage?: unknown; usage?: unknown };
|
|
488
|
+
|
|
489
|
+
function addAssistantMessageUsage(total: Usage, message: unknown): void {
|
|
490
|
+
const assistant = message as AssistantLikeMessage;
|
|
491
|
+
if (assistant?.role !== "assistant" || !assistant.usage || typeof assistant.usage !== "object") return;
|
|
492
|
+
addUsage(total, assistant.usage as Usage);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function aggregateAssistantUsage(messages: unknown[]): Usage {
|
|
496
|
+
const total = createEmptyUsage();
|
|
497
|
+
for (const message of messages) addAssistantMessageUsage(total, message);
|
|
498
|
+
return total;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function addSessionEventUsage(total: Usage, event: unknown): void {
|
|
502
|
+
const sessionEvent = event as { type?: string; message?: unknown; result?: { usage?: Usage } };
|
|
503
|
+
if (sessionEvent?.type === "message_end") return void addAssistantMessageUsage(total, sessionEvent.message);
|
|
504
|
+
if (sessionEvent?.type === "compaction_end" && sessionEvent.result?.usage) addUsage(total, sessionEvent.result.usage);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function inspectFinalAssistant(messages: unknown[]):
|
|
508
|
+
| { ok: true; answer: string; stopReason?: string }
|
|
509
|
+
| { ok: false; reason: string; stopReason?: string; errorMessage?: string } {
|
|
467
510
|
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
468
|
-
const message = messages[i] as
|
|
469
|
-
if (message?.role !== "assistant"
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
511
|
+
const message = messages[i] as AssistantLikeMessage;
|
|
512
|
+
if (message?.role !== "assistant") continue;
|
|
513
|
+
const stopReason = typeof message.stopReason === "string" ? message.stopReason.trim() : "";
|
|
514
|
+
const errorMessage = typeof message.errorMessage === "string" ? message.errorMessage.trim() : "";
|
|
515
|
+
const answer = Array.isArray(message.content)
|
|
516
|
+
? message.content
|
|
517
|
+
.map((part) => (part && typeof part === "object" && (part as { type?: string }).type === "text" && typeof (part as { text?: unknown }).text === "string"
|
|
518
|
+
? (part as { text: string }).text
|
|
519
|
+
: ""))
|
|
520
|
+
.join("")
|
|
521
|
+
.trim()
|
|
522
|
+
: "";
|
|
523
|
+
if (stopReason === "error") return { ok: false, reason: `code_reviewer subagent error: ${errorMessage || "provider/model error"}`, stopReason, errorMessage };
|
|
524
|
+
if (stopReason === "aborted") return { ok: false, reason: "code_reviewer subagent aborted before producing a usable answer", stopReason };
|
|
525
|
+
if (answer) return { ok: true, answer, stopReason: stopReason || undefined };
|
|
526
|
+
return { ok: false, reason: stopReason ? `code_reviewer subagent produced no final assistant text (stopReason: ${stopReason})` : "code_reviewer subagent produced no final assistant text", stopReason: stopReason || undefined, errorMessage: errorMessage || undefined };
|
|
478
527
|
}
|
|
479
|
-
return "";
|
|
528
|
+
return { ok: false, reason: "code_reviewer subagent produced no assistant message" };
|
|
480
529
|
}
|
|
481
530
|
|
|
482
|
-
function
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
return /aborted|cancelled|canceled/i.test(message);
|
|
531
|
+
function classifyRunFailure(error: unknown, aborted: boolean): { status: "aborted" | "error"; message: string; error?: string } {
|
|
532
|
+
const message = aborted ? "Aborted" : error instanceof Error ? error.message : String(error);
|
|
533
|
+
return { status: aborted ? "aborted" : "error", message, error: aborted ? undefined : message };
|
|
486
534
|
}
|
|
487
535
|
|
|
488
536
|
function isInside(parent: string, child: string): boolean {
|
|
@@ -1086,9 +1134,21 @@ export const __test__ = {
|
|
|
1086
1134
|
normalizeThinkingLevel,
|
|
1087
1135
|
resolveThinkingLevel,
|
|
1088
1136
|
selectCodeReviewerModel,
|
|
1137
|
+
aggregateAssistantUsage,
|
|
1138
|
+
addAssistantMessageUsage,
|
|
1139
|
+
addSessionEventUsage,
|
|
1140
|
+
inspectFinalAssistant,
|
|
1141
|
+
classifyRunFailure,
|
|
1089
1142
|
};
|
|
1090
1143
|
|
|
1091
1144
|
export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
1145
|
+
pi.on("tool_result", async (event) => {
|
|
1146
|
+
if (event.toolName !== "code_reviewer") return undefined;
|
|
1147
|
+
const details = event.details as ReviewDetails | undefined;
|
|
1148
|
+
if (details?.status !== "error") return undefined;
|
|
1149
|
+
return { isError: true };
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1092
1152
|
pi.registerTool({
|
|
1093
1153
|
name: "code_reviewer",
|
|
1094
1154
|
label: "Code reviewer",
|
|
@@ -1126,24 +1186,29 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1126
1186
|
let unsubscribe: (() => void) | undefined;
|
|
1127
1187
|
let runTimeout: NodeJS.Timeout | undefined;
|
|
1128
1188
|
let abortListenerAdded = false;
|
|
1129
|
-
let
|
|
1189
|
+
let callerAborted = Boolean(signal?.aborted);
|
|
1190
|
+
const usage = createEmptyUsage();
|
|
1130
1191
|
|
|
1131
1192
|
const emit = () => {
|
|
1132
1193
|
onUpdate?.({ content: [{ type: "text", text: lastContent }], details });
|
|
1133
1194
|
};
|
|
1134
1195
|
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1196
|
+
const abortForCleanup = () => {
|
|
1197
|
+
void session?.abort();
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
const abortFromCaller = () => {
|
|
1201
|
+
callerAborted = true;
|
|
1137
1202
|
details.status = "aborted";
|
|
1138
1203
|
details.endedAt = Date.now();
|
|
1139
1204
|
lastContent = "Aborted";
|
|
1140
1205
|
emit();
|
|
1141
|
-
|
|
1206
|
+
abortForCleanup();
|
|
1142
1207
|
};
|
|
1143
1208
|
|
|
1144
|
-
if (signal?.aborted)
|
|
1209
|
+
if (signal?.aborted) abortFromCaller();
|
|
1145
1210
|
if (signal && !signal.aborted) {
|
|
1146
|
-
signal.addEventListener("abort",
|
|
1211
|
+
signal.addEventListener("abort", abortFromCaller);
|
|
1147
1212
|
abortListenerAdded = true;
|
|
1148
1213
|
}
|
|
1149
1214
|
|
|
@@ -1199,6 +1264,7 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1199
1264
|
attemptSession = created.session;
|
|
1200
1265
|
session = attemptSession;
|
|
1201
1266
|
attemptUnsubscribe = attemptSession.subscribe((event) => {
|
|
1267
|
+
addSessionEventUsage(usage, event);
|
|
1202
1268
|
switch (event.type) {
|
|
1203
1269
|
case "turn_end":
|
|
1204
1270
|
details.turns += 1;
|
|
@@ -1235,20 +1301,29 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1235
1301
|
});
|
|
1236
1302
|
unsubscribe = attemptUnsubscribe;
|
|
1237
1303
|
|
|
1238
|
-
if (!
|
|
1304
|
+
if (!callerAborted) {
|
|
1239
1305
|
const promptPromise = attemptSession.prompt(buildUserPrompt(input, cwd), { expandPromptTemplates: false });
|
|
1240
1306
|
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
|
1241
1307
|
runTimeout = setTimeout(() => {
|
|
1242
|
-
|
|
1308
|
+
abortForCleanup();
|
|
1243
1309
|
reject(new Error(`code_reviewer timed out after ${Math.round(MAX_RUN_MS / 1000)} seconds.`));
|
|
1244
1310
|
}, MAX_RUN_MS);
|
|
1245
1311
|
});
|
|
1246
1312
|
await Promise.race([promptPromise, timeoutPromise]);
|
|
1247
1313
|
}
|
|
1248
1314
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1315
|
+
const outcome = inspectFinalAssistant(attemptSession.state.messages);
|
|
1316
|
+
if (outcome.ok) {
|
|
1317
|
+
answer = outcome.answer;
|
|
1318
|
+
succeeded = true;
|
|
1319
|
+
break;
|
|
1320
|
+
}
|
|
1321
|
+
lastAttemptError = outcome.reason;
|
|
1322
|
+
const canFallBack =
|
|
1323
|
+
index < modelSelection.ordered.length - 1 &&
|
|
1324
|
+
outcome.stopReason === "error" &&
|
|
1325
|
+
isModelAvailabilityError(outcome.errorMessage || outcome.reason);
|
|
1326
|
+
if (!canFallBack) break;
|
|
1252
1327
|
} catch (error) {
|
|
1253
1328
|
const message = error instanceof Error ? error.message : String(error);
|
|
1254
1329
|
lastAttemptError = message;
|
|
@@ -1267,26 +1342,34 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1267
1342
|
}
|
|
1268
1343
|
}
|
|
1269
1344
|
}
|
|
1270
|
-
if (!answer
|
|
1271
|
-
|
|
1272
|
-
|
|
1345
|
+
if (!answer) {
|
|
1346
|
+
if (callerAborted) {
|
|
1347
|
+
lastContent = "Aborted";
|
|
1348
|
+
details.status = "aborted";
|
|
1349
|
+
} else {
|
|
1350
|
+
throw new Error(lastAttemptError || "the isolated code_reviewer session finished without a usable answer");
|
|
1351
|
+
}
|
|
1352
|
+
} else {
|
|
1353
|
+
lastContent = answer;
|
|
1354
|
+
details.status = "done";
|
|
1355
|
+
}
|
|
1273
1356
|
details.endedAt = Date.now();
|
|
1274
1357
|
const report = appendRunDetails(lastContent, details);
|
|
1275
1358
|
lastContent = report;
|
|
1276
1359
|
emit();
|
|
1277
|
-
return { content: [{ type: "text", text: report }], details };
|
|
1360
|
+
return { content: [{ type: "text", text: report }], details, usage };
|
|
1278
1361
|
} catch (error) {
|
|
1279
|
-
const
|
|
1280
|
-
const message =
|
|
1281
|
-
details.status =
|
|
1282
|
-
details.error =
|
|
1362
|
+
const failure = classifyRunFailure(error, callerAborted);
|
|
1363
|
+
const { message } = failure;
|
|
1364
|
+
details.status = failure.status;
|
|
1365
|
+
details.error = failure.error;
|
|
1283
1366
|
details.endedAt = Date.now();
|
|
1284
1367
|
lastContent = appendRunDetails(`## Verdict\nReview failed.\n\n## Findings\n- major — Review execution failed. Evidence: ${message}. Why it matters: the requested read-only review did not complete.\n\n## Validation\n- none\n\n## Scope check\nReview could not be completed.\n\n## Run details\n- failure: ${message}`, details);
|
|
1285
1368
|
emit();
|
|
1286
|
-
return { content: [{ type: "text", text: lastContent }], details };
|
|
1369
|
+
return { content: [{ type: "text", text: lastContent }], details, usage };
|
|
1287
1370
|
} finally {
|
|
1288
1371
|
if (runTimeout) clearTimeout(runTimeout);
|
|
1289
|
-
if (signal && abortListenerAdded) signal.removeEventListener("abort",
|
|
1372
|
+
if (signal && abortListenerAdded) signal.removeEventListener("abort", abortFromCaller);
|
|
1290
1373
|
unsubscribe?.();
|
|
1291
1374
|
session?.dispose();
|
|
1292
1375
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.82.0
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A small pi extension that asks for confirmation before destructive session actions.
|
|
4
4
|
|
|
5
|
-
This is adapted from the original `confirm-destructive.ts` example in [`earendil-works/pi
|
|
5
|
+
This is adapted from the original `confirm-destructive.ts` example in [`earendil-works/pi`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/confirm-destructive.ts) and kept basically the same.
|
|
6
6
|
|
|
7
7
|
## What it checks
|
|
8
8
|
|
|
@@ -5,7 +5,24 @@
|
|
|
5
5
|
* Demonstrates how to cancel session events using the before_* events.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ExtensionAPI, SessionBeforeSwitchEvent,
|
|
8
|
+
import type { ExtensionAPI, SessionBeforeSwitchEvent, SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
|
|
10
|
+
function hasUnsavedUserWork(entries: SessionEntry[]): boolean {
|
|
11
|
+
let sawAssistant = false;
|
|
12
|
+
|
|
13
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
14
|
+
const entry = entries[index];
|
|
15
|
+
if (entry.type !== "message") continue;
|
|
16
|
+
if (entry.message.role === "assistant") {
|
|
17
|
+
sawAssistant = true;
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
if (entry.message.role === "user") return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (sawAssistant) return false;
|
|
24
|
+
return entries.some((entry) => entry.type === "message" && entry.message.role === "user");
|
|
25
|
+
}
|
|
9
26
|
|
|
10
27
|
export default function (pi: ExtensionAPI) {
|
|
11
28
|
pi.on("session_before_switch", async (event: SessionBeforeSwitchEvent, ctx) => {
|
|
@@ -24,11 +41,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
24
41
|
return;
|
|
25
42
|
}
|
|
26
43
|
|
|
27
|
-
// reason === "resume" - check if there are unsaved changes (messages
|
|
44
|
+
// reason === "resume" - check if there are unsaved changes (user messages newer than the last assistant response)
|
|
28
45
|
const entries = ctx.sessionManager.getEntries();
|
|
29
|
-
const hasUnsavedWork = entries
|
|
30
|
-
(e): e is SessionMessageEntry => e.type === "message" && e.message.role === "user",
|
|
31
|
-
);
|
|
46
|
+
const hasUnsavedWork = hasUnsavedUserWork(entries);
|
|
32
47
|
|
|
33
48
|
if (hasUnsavedWork) {
|
|
34
49
|
const confirmed = await ctx.ui.confirm(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.82.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.82.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.82.0
|
|
@@ -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 read-only contrarian subagent 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
|
|