pi-goal-list-loop-audit 0.31.7 → 0.31.9

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.
@@ -163,6 +163,7 @@ function buildGoalAuditorPrompt(goal: Goal, completionSummary: string | null | u
163
163
  "6. When you disapprove, end the report body with a '## Required fixes' section: one line per blocking gap, each an actionable instruction the executor can complete (most critical first). This tail is what the executor sees first — make it self-sufficient.",
164
164
  "7. Write the report in English, and never emit <think> blocks or fragments — your reasoning stays private; the report is the verdict plus evidence.",
165
165
  "8. End with exactly <approved/> only if the objective is truly complete; <impossible>reason</impossible> if it can never be satisfied as stated; otherwise end with exactly <disapproved/>.",
166
+ "9. Reject-class pattern (v0.31.9, field-observed fork bomb): a test that invokes the project's whole test runner from INSIDE the suite (a test file spawning `bun test`/`npm test`/`pytest`/etc. on a path the runner itself collects) is unbounded recursion — 521 processes, load 28, a full system crash; a `timeout` wrapper kills processes, not recursion depth. Disapprove unless the recursion is provably depth-capped (e.g. an env sentinel checked before spawning).",
166
167
  ...(goal.verificationContract?.trim()
167
168
  ? [
168
169
  "",
@@ -31,7 +31,7 @@ export interface Settings {
31
31
  * the first-order defense either way; diversity is the second-order one
32
32
  * the user may deliberately trade away. */
33
33
  auditorSameSessionSwap?: boolean;
34
- auditorThinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
34
+ auditorThinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
35
35
  /** Shell command run on goal complete / goal pause / loop stop; message passed as $1. */
36
36
  notifyCmd?: string;
37
37
  /** Per-goal token budget; crossing it pauses the goal. Off by default
@@ -1380,7 +1380,7 @@ async function retryStoredCompletionAudit(ctx: ExtensionContext, origin: "quota-
1380
1380
  completionSummary: claim.completionSummary,
1381
1381
  verificationSummary: claim.verificationSummary,
1382
1382
  model: auditorModel,
1383
- thinkingLevel: settings.auditorThinkingLevel ?? "high",
1383
+ thinkingLevel: (settings.auditorThinkingLevel ?? "high") as any, // may be "max" — pi ≥0.83 understands it; the dev-types predate it
1384
1384
  onProgress: (progress) => {
1385
1385
  latestAuditProgress = { currentTool: progress.currentTool, label: progress.label, elapsedMs: progress.elapsedMs, lastEventAt: Date.now() };
1386
1386
  refreshUI(liveCtx);
@@ -3188,7 +3188,7 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
3188
3188
  completionSummary: p.completionSummary,
3189
3189
  verificationSummary: p.verificationSummary,
3190
3190
  model: auditorModel,
3191
- thinkingLevel: settings.auditorThinkingLevel ?? "high",
3191
+ thinkingLevel: (settings.auditorThinkingLevel ?? "high") as any, // may be "max" — pi ≥0.83 understands it; the dev-types predate it
3192
3192
  signal: signal ?? undefined,
3193
3193
  onProgress: (progress) => {
3194
3194
  latestAuditProgress = {
@@ -4229,6 +4229,24 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
4229
4229
  * bought; it lasted one version). Every hop is LOUD (ledger + notify): the
4230
4230
  * v0.9.12 no-SILENT-substitution law.
4231
4231
  */
4232
+ /** v0.31.8: the auditor thinking options are derived from the PICKED
4233
+ * model, not a hardcoded list — same rule as pi's own thinking selector
4234
+ * (pi-ai getSupportedThinkingLevels): non-reasoning models expose only
4235
+ * "off"; xhigh/max exist only when the model maps them (thinkingLevelMap).
4236
+ * Replicated inline so the extension's older pi-ai dev-types don't matter —
4237
+ * the fields are read at runtime from the user's installed pi. */
4238
+ function auditorThinkingLevels(model: any): string[] {
4239
+ const ALL = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
4240
+ if (!model?.reasoning) return ["off"];
4241
+ const map = model.thinkingLevelMap as Record<string, string | null> | undefined;
4242
+ return ALL.filter((level) => {
4243
+ const mapped = map?.[level];
4244
+ if (mapped === null) return false;
4245
+ if (level === "xhigh" || level === "max") return mapped !== undefined;
4246
+ return true;
4247
+ });
4248
+ }
4249
+
4232
4250
  function resolveAuditorModel(ctx: ExtensionContext, ref?: string, fallbackRef?: string, sameSessionSwap = true): { model: any; error?: string; via?: string } {
4233
4251
  const sessionModel = ctx.model as any;
4234
4252
  const tryRef = (trimmed: string): { model?: any; reason?: string } => {
@@ -4443,17 +4461,26 @@ export async function handleSettingChoice(id: string, ctx: ExtensionContext): Pr
4443
4461
  // v0.31.7: the select must be UNMISTAKABLY the auditor's — the user
4444
4462
  // Esc'd through it because it read like pi's own (general) thinking
4445
4463
  // dialog, and nothing was ever saved.
4464
+ // v0.31.8: the options come from the PICKED MODEL's info (user: "we
4465
+ // are not using the model information cause it has no max") — a model
4466
+ // that maps xhigh/max offers them; a non-reasoning model is told, not asked.
4467
+ let pickedModel: any = pick.kind === "session" ? (ctx.model as any) : undefined;
4468
+ if (pick.kind === "ref" && pick.ref) {
4469
+ const parts = pick.ref.split("/");
4470
+ try {
4471
+ pickedModel = parts.length === 2 ? (ctx.modelRegistry?.find?.(parts[0]!, parts[1]!) as any) : (ctx.modelRegistry?.getAvailable?.().filter((m: any) => m.id === pick.ref)[0] as any);
4472
+ } catch { pickedModel = undefined; } // levels fall back to the full ladder below
4473
+ }
4446
4474
  const curThinking = loadSettings(ctx.cwd).auditorThinkingLevel;
4475
+ const levels = auditorThinkingLevels(pickedModel);
4476
+ if (levels.length <= 1) {
4477
+ ctx.ui.notify(`Auditor model: ${pick.kind === "session" ? "session model (override cleared)" : pick.ref} — this model exposes no thinking levels (auditor runs with thinking off).`, "info");
4478
+ return;
4479
+ }
4480
+ const DESCR: Record<string, string> = { off: "no reasoning", minimal: "~1k tokens", low: "~2k tokens", medium: "~8k tokens", high: "the default; the gate must not ride the session's coding dial", xhigh: "~32k tokens", max: "maximum reasoning" };
4447
4481
  const t = await ctx.ui.select(
4448
4482
  "Auditor thinking — ISOLATED auditor session ONLY (your session model's thinking is untouched)",
4449
- [
4450
- `high — the default; the gate must not ride the session's coding dial${curThinking === undefined || curThinking === "high" ? " (current)" : ""}`,
4451
- `medium${curThinking === "medium" ? " (current)" : ""}`,
4452
- `low${curThinking === "low" ? " (current)" : ""}`,
4453
- `minimal${curThinking === "minimal" ? " (current)" : ""}`,
4454
- `xhigh${curThinking === "xhigh" ? " (current)" : ""}`,
4455
- `off${curThinking === "off" ? " (current)" : ""}`,
4456
- ],
4483
+ levels.map((lv) => `${lv} — ${DESCR[lv] ?? ""}${lv === "high" && curThinking === undefined ? " (current)" : curThinking === lv ? " (current)" : ""}`),
4457
4484
  );
4458
4485
  if (t) saveSettings("global", ctx.cwd, { auditorThinkingLevel: t.split(" ")[0] as Settings["auditorThinkingLevel"] });
4459
4486
  ctx.ui.notify(`Auditor model: ${pick.kind === "session" ? "session model (override cleared)" : pick.ref}${t ? ` · thinking ${t.split(" ")[0]}` : ""}`, "info");
@@ -5370,7 +5397,7 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
5370
5397
  }
5371
5398
  }
5372
5399
  } else if (key === "thinking" || key === "auditorthinkinglevel") {
5373
- if (["off", "minimal", "low", "medium", "high", "xhigh"].includes(value)) {
5400
+ if (["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(value)) {
5374
5401
  patch.auditorThinkingLevel = value as Settings["auditorThinkingLevel"];
5375
5402
  changed = true;
5376
5403
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.31.7",
3
+ "version": "0.31.9",
4
4
  "description": "Goal. Loop. Audit. Done. \u2014 a pi-coding-agent extension that supervises long-running work, with isolated auditor on each completion. Beat bamboozling by design: the auditor runs in a fresh session with no extensions, no skills, no editor \u2014 only the read tools needed to verify your goal.",
5
5
  "license": "MIT",
6
6
  "author": "dracon",
@@ -151,6 +151,7 @@ When the user must CHOOSE between paths, use `pause_goal` with `kind="decision"`
151
151
  - **Do not modify the objective silently.** The objective is the user's; if it has drifted from what makes sense, use `complete_goal`'s `newObjective` at completion time, or `pause_goal` and propose a `/goal tweak` mid-flight — never just work on something else and claim the original.
152
152
  - **Do not pretend completion.** If verification evidence is missing, call `pause_goal` instead of `complete_goal`.
153
153
  - **Git discipline: never touch identity or branches.** Commit with the repo's configured identity exactly as-is — no `git config user.*`, no per-commit `git -c user.name=…` overrides, no invented identities like `<task>-agent <…@local>` (field-observed: a phase agent branded itself `phase-e-agent <phase-e@local>` and polluted the history). No creating or switching branches either — commit on the branch you found (usually `main`) and push to its upstream. If git refuses a commit for a missing identity, STOP and ask the user — never invent one.
154
+ - **Never run the suite from inside the suite.** A test must not spawn the project's whole test runner (`bun test`, `npm test`, `pytest`…) from a file the runner itself collects — unbounded recursion is a fork bomb (field-observed 2026-07-31: 521 processes, load 28, a full system crash). Count test files or parse manifests; never re-invoke the runner on its own suite.
154
155
  - **Do not polish doorknobs.** If you are out of work and the goal is satisfied, call `complete_goal` instead of inventing a side-improvement.
155
156
  - **Do not give up early.** If a task is hard, run it down properly. The auditor will catch doorknobs; the agent's job is to do the real work.
156
157
 
@@ -54,3 +54,5 @@ ${STRATEGY_NOTE}
54
54
  - The spec is ALIVE: if the target needs sharpening, call
55
55
  propose_loop_refine with your rationale — the user confirms or rejects.
56
56
  ${BOUNDS_NOTE}
57
+
58
+ - **Never run the suite from inside the suite.** A test must not spawn the project's whole test runner (`bun test`, `npm test`, `pytest`…) from a file the runner itself collects — unbounded recursion is a fork bomb (field-observed 2026-07-31: 521 processes, load 28, a full system crash). Count test files or parse manifests; never re-invoke the runner on its own suite.
@@ -57,3 +57,5 @@ ${STRATEGY_NOTE}
57
57
  when the metric plateaus. Keep making real improvements.
58
58
  - If the measure command itself is broken (errors, no number), fix whatever
59
59
  your last change broke — that counts as a stall.
60
+
61
+ - **Never run the suite from inside the suite.** A test must not spawn the project's whole test runner (`bun test`, `npm test`, `pytest`…) from a file the runner itself collects — unbounded recursion is a fork bomb (field-observed 2026-07-31: 521 processes, load 28, a full system crash). Count test files or parse manifests; never re-invoke the runner on its own suite.