05fbc6019e790ed9f6937ac6bc1d9465624c842e

Author
TheEdgeOfRage <git@theedgeofrage.com>
Committer
TheEdgeOfRage <git@theedgeofrage.com>
Date

Message

prettier formatting

Diff

This diff is truncated to protect this page.

   1diff --git a/.prettierrc b/.prettierrc
   2new file mode 100644
   3index 0000000000000000000000000000000000000000..b48917cda44fac62bbd9838c9b91cf0f7cd1abea
   4--- /dev/null
   5+++ b/.prettierrc
   6@@ -0,0 +1,7 @@
   7+{
   8+  "useTabs": false,
   9+  "singleQuote": false,
  10+  "trailingComma": "all",
  11+  "printWidth": 120,
  12+  "plugins": []
  13+}
  14diff --git a/AGENTS.md b/AGENTS.md
  15index f3f312530f39d8b8b594ae48461a768a3624dcaa..cd996c6ae85fb84f51ad12e31ce196619ea40e9f 100644
  16--- a/AGENTS.md
  17+++ b/AGENTS.md
  18@@ -41,6 +41,7 @@ When changing deterministic patterns or matching behavior:
  19 
  20 ```sh
  21 bun test
  22+bun prettier -w .
  23 bunx eslint src test
  24 bunx tsc --noEmit
  25 ```
  26diff --git a/README.md b/README.md
  27index 3f4f9b2f631e412146fb4f8aeae87730f9af58bf..55c26ecae1b4958cb000ad86fb3d3f8ff7774d54 100644
  28--- a/README.md
  29+++ b/README.md
  30@@ -32,13 +32,13 @@ Use a Pi-configured model. This uses Pi's provider transport and authentication,
  31 
  32 ```json
  33 {
  34-	"reviewer": {
  35-		"kind": "pi",
  36-		"provider": "openai-codex",
  37-		"model": "gpt-5.6-luna",
  38-		"reasoningEffort": "minimal",
  39-		"promptCacheKey": "policy-engine-v1"
  40-	}
  41+  "reviewer": {
  42+    "kind": "pi",
  43+    "provider": "openai-codex",
  44+    "model": "gpt-5.6-luna",
  45+    "reasoningEffort": "minimal",
  46+    "promptCacheKey": "policy-engine-v1"
  47+  }
  48 }
  49 ```
  50 
  51@@ -50,12 +50,12 @@ Use local llama.cpp. This backend does not use authentication.
  52 
  53 ```json
  54 {
  55-	"reviewer": {
  56-		"kind": "llama.cpp",
  57-		"baseUrl": "http://127.0.0.1:9931",
  58-		"model": "reviewer",
  59-		"enableThinking": true
  60-	}
  61+  "reviewer": {
  62+    "kind": "llama.cpp",
  63+    "baseUrl": "http://127.0.0.1:9931",
  64+    "model": "reviewer",
  65+    "enableThinking": true
  66+  }
  67 }
  68 ```
  69 
  70@@ -74,9 +74,7 @@ Pi uses a root-level `tools` map and `externalDirectories` list. Any tool name c
  71     "read": "allow",
  72     "my_extension_tool": "allow"
  73   },
  74-  "externalDirectories": [
  75-    "/tmp/pi"
  76-  ]
  77+  "externalDirectories": ["/tmp/pi"]
  78 }
  79 ```
  80 
  81@@ -114,7 +112,6 @@ POLICY_EVAL_REASONING_EFFORT=minimal \
  82 bun test test/core/llm.test.ts
  83 ```
  84 
  85-
  86 Pi evaluations using Pi's OpenAI Responses APIs use `policy-engine-evaluation-v1` as the cache key; set `POLICY_EVAL_PROMPT_CACHE_KEY` to override it.
  87 
  88 Tests redirect Pi state to a temporary directory, except opt-in Pi OAuth evaluations, which use the existing Pi OAuth session.
  89diff --git a/bun.lock b/bun.lock
  90index a2298473651561f90a6c4f14cf9c3915dd551850..5609fefbe2dae566eaadddb743a2300a80f91b35 100644
  91--- a/bun.lock
  92+++ b/bun.lock
  93@@ -13,6 +13,7 @@
  94         "@eslint/js": "^10.0.1",
  95         "bun-types": "latest",
  96         "eslint": "^10.1.0",
  97+        "prettier": "3.9.6",
  98         "typescript": "^6.0.2",
  99         "typescript-eslint": "^8.58.0",
 100       },
 101@@ -384,6 +385,8 @@
 102 
 103     "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
 104 
 105+    "prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="],
 106+
 107     "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
 108 
 109diff --git a/eslint.config.js b/eslint.config.js
 110index b519dda02dd81259febb00781320261b091a692c..fc1f8d5ef22b9efc2d3d6f339cb3f15821c5006c 100644
 111--- a/eslint.config.js
 112+++ b/eslint.config.js
 113@@ -1,5 +1,5 @@
 114-import eslint from "@eslint/js"
 115-import tseslint from "typescript-eslint"
 116+import eslint from "@eslint/js";
 117+import tseslint from "typescript-eslint";
 118 
 119 export default tseslint.config(
 120   eslint.configs.recommended,
 121@@ -14,4 +14,4 @@ export default tseslint.config(
 122       "no-empty": ["error", { allowEmptyCatch: true }],
 123     },
 124   },
 125-)
 126+);
 127diff --git a/package.json b/package.json
 128index ae2566315559f0718a2170b77083f9e09302ba22..2ae90170e191e4c8b4ed44e1caa0600257307d7b 100644
 129--- a/package.json
 130+++ b/package.json
 131@@ -24,6 +24,7 @@
 132     "@eslint/js": "^10.0.1",
 133     "bun-types": "latest",
 134     "eslint": "^10.1.0",
 135+    "prettier": "3.9.6",
 136     "typescript": "^6.0.2",
 137     "typescript-eslint": "^8.58.0"
 138   },
 139diff --git a/src/core/audit.ts b/src/core/audit.ts
 140index 9cd7bd1aaafebbbb3de7684bcf26828df20c9789..46cfc5e9e18a308cb56f2bd1e3707638e23e2840 100644
 141--- a/src/core/audit.ts
 142+++ b/src/core/audit.ts
 143@@ -1,19 +1,18 @@
 144-import { appendFile, mkdir } from "node:fs/promises"
 145-import { dirname } from "node:path"
 146-import type { DecisionAudit } from "./types"
 147+import { appendFile, mkdir } from "node:fs/promises";
 148+import { dirname } from "node:path";
 149+import type { DecisionAudit } from "./types";
 150 
 151 export function createJsonlDecisionAudit(file: string): DecisionAudit {
 152   return {
 153     async record(entry): Promise<void> {
 154       try {
 155-        await mkdir(dirname(file), { recursive: true })
 156-        await appendFile(file, `${JSON.stringify({ recordedAt: new Date().toISOString(), ...entry })}\n`)
 157-      } catch {
 158-      }
 159+        await mkdir(dirname(file), { recursive: true });
 160+        await appendFile(file, `${JSON.stringify({ recordedAt: new Date().toISOString(), ...entry })}\n`);
 161+      } catch {}
 162     },
 163-  }
 164+  };
 165 }
 166 
 167 export const disabledDecisionAudit: DecisionAudit = {
 168   record: async () => {},
 169-}
 170+};
 171diff --git a/src/core/cache.ts b/src/core/cache.ts
 172index be7ac7d8185b9d848418affe4e44ea78ca5cb08e..e85e9ed26d7d437de67e2b0b9644229ba540b473 100644
 173--- a/src/core/cache.ts
 174+++ b/src/core/cache.ts
 175@@ -1,41 +1,38 @@
 176-import { appendFile, mkdir, readFile } from "node:fs/promises"
 177-import { dirname } from "node:path"
 178-import { POLICY_VERSION } from "./rules"
 179-import type { CacheEntry, Decision, DecisionCache } from "./types"
 180+import { appendFile, mkdir, readFile } from "node:fs/promises";
 181+import { dirname } from "node:path";
 182+import { POLICY_VERSION } from "./rules";
 183+import type { CacheEntry, Decision, DecisionCache } from "./types";
 184 
 185 type StoredCacheEntry = CacheEntry & {
 186-  policyVersion: number
 187-}
 188+  policyVersion: number;
 189+};
 190 
 191 export function createJsonlDecisionCache(file: string): DecisionCache {
 192   return {
 193     async lookup(key: string): Promise<Decision | undefined> {
 194       try {
 195-        const content = await readFile(file, "utf8")
 196+        const content = await readFile(file, "utf8");
 197         for (const line of content.split("\n")) {
 198-          if (!line) continue
 199+          if (!line) continue;
 200           try {
 201-            const entry = JSON.parse(line) as StoredCacheEntry
 202-            if (entry.key === key && entry.policyVersion === POLICY_VERSION) return entry.decision
 203-          } catch {
 204-          }
 205+            const entry = JSON.parse(line) as StoredCacheEntry;
 206+            if (entry.key === key && entry.policyVersion === POLICY_VERSION) return entry.decision;
 207+          } catch {}
 208         }
 209-      } catch {
 210-      }
 211-      return undefined
 212+      } catch {}
 213+      return undefined;
 214     },
 215     async write(entry: CacheEntry): Promise<void> {
 216       try {
 217-        await mkdir(dirname(file), { recursive: true })
 218-        const stored: StoredCacheEntry = { ...entry, policyVersion: POLICY_VERSION }
 219-        await appendFile(file, `${JSON.stringify(stored)}\n`)
 220-      } catch {
 221-      }
 222+        await mkdir(dirname(file), { recursive: true });
 223+        const stored: StoredCacheEntry = { ...entry, policyVersion: POLICY_VERSION };
 224+        await appendFile(file, `${JSON.stringify(stored)}\n`);
 225+      } catch {}
 226     },
 227-  }
 228+  };
 229 }
 230 
 231 export const disabledDecisionCache: DecisionCache = {
 232   lookup: async () => undefined,
 233   write: async () => {},
 234-}
 235+};
 236diff --git a/src/core/config.ts b/src/core/config.ts
 237index 45a6b36099f7c3d2879f377d5267f96a00396756..8c585217cb6fe564de3c345e65bd68896317d13f 100644
 238--- a/src/core/config.ts
 239+++ b/src/core/config.ts
 240@@ -1,59 +1,65 @@
 241-import type { PiReasoningEffort, ReviewerConfig } from "./types"
 242+import type { PiReasoningEffort, ReviewerConfig } from "./types";
 243 
 244 function objectValue(value: unknown): Record<string, unknown> | undefined {
 245-  return value && typeof value === "object" && !Array.isArray(value)
 246-    ? value as Record<string, unknown>
 247-    : undefined
 248+  return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
 249 }
 250 
 251 function stringValue(value: unknown): string | undefined {
 252-  return typeof value === "string" && value.trim() ? value.trim() : undefined
 253+  return typeof value === "string" && value.trim() ? value.trim() : undefined;
 254 }
 255 
 256 function booleanValue(value: unknown, field: string, fallback: boolean): boolean {
 257-  if (value === undefined) return fallback
 258-  if (typeof value === "boolean") return value
 259-  throw new Error(`${field} must be a boolean`)
 260+  if (value === undefined) return fallback;
 261+  if (typeof value === "boolean") return value;
 262+  throw new Error(`${field} must be a boolean`);
 263 }
 264 
 265 function reasoningEffort(value: unknown): PiReasoningEffort | undefined {
 266-  if (value === undefined) return undefined
 267-  if (value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max") {
 268-    return value
 269+  if (value === undefined) return undefined;
 270+  if (
 271+    value === "none" ||
 272+    value === "minimal" ||
 273+    value === "low" ||
 274+    value === "medium" ||
 275+    value === "high" ||
 276+    value === "xhigh" ||
 277+    value === "max"
 278+  ) {
 279+    return value;
 280   }
 281-  throw new Error('reviewer.reasoningEffort must be "none", "minimal", "low", "medium", "high", "xhigh", or "max"')
 282+  throw new Error('reviewer.reasoningEffort must be "none", "minimal", "low", "medium", "high", "xhigh", or "max"');
 283 }
 284 
 285 export function parseReviewerConfig(value: unknown): ReviewerConfig {
 286-  const data = objectValue(value)
 287-  const reviewer = data && objectValue(data.reviewer)
 288+  const data = objectValue(value);
 289+  const reviewer = data && objectValue(data.reviewer);
 290   if (!reviewer) {
 291     return {
 292       kind: "pi",
 293       provider: "openai-codex",
 294       model: "gpt-5.6-luna",
 295       reasoningEffort: "minimal",
 296-    }
 297+    };
 298   }
 299-  const model = stringValue(reviewer.model)
 300-  if (!model) throw new Error("reviewer.model must be a non-empty string")
 301+  const model = stringValue(reviewer.model);
 302+  if (!model) throw new Error("reviewer.model must be a non-empty string");
 303   if (reviewer.kind === "llama.cpp") {
 304-    const baseUrl = stringValue(reviewer.baseUrl)
 305-    if (!baseUrl) throw new Error("reviewer.baseUrl must be a non-empty string")
 306+    const baseUrl = stringValue(reviewer.baseUrl);
 307+    if (!baseUrl) throw new Error("reviewer.baseUrl must be a non-empty string");
 308     return {
 309       kind: "llama.cpp",
 310       baseUrl,
 311       model,
 312       enableThinking: booleanValue(reviewer.enableThinking, "reviewer.enableThinking", false),
 313-    }
 314+    };
 315   }
 316-  if (reviewer.kind !== "pi") throw new Error('reviewer.kind must be "pi" or "llama.cpp"')
 317+  if (reviewer.kind !== "pi") throw new Error('reviewer.kind must be "pi" or "llama.cpp"');
 318 
 319-  const provider = stringValue(reviewer.provider)
 320-  if (!provider) throw new Error("reviewer.provider must be a non-empty string")
 321-  const promptCacheKey = stringValue(reviewer.promptCacheKey)
 322+  const provider = stringValue(reviewer.provider);
 323+  if (!provider) throw new Error("reviewer.provider must be a non-empty string");
 324+  const promptCacheKey = stringValue(reviewer.promptCacheKey);
 325   if (reviewer.promptCacheKey !== undefined && !promptCacheKey) {
 326-    throw new Error("reviewer.promptCacheKey must be a non-empty string")
 327+    throw new Error("reviewer.promptCacheKey must be a non-empty string");
 328   }
 329   return {
 330     kind: "pi",
 331@@ -61,5 +67,5 @@ export function parseReviewerConfig(value: unknown): ReviewerConfig {
 332     model,
 333     reasoningEffort: reasoningEffort(reviewer.reasoningEffort),
 334     promptCacheKey,
 335-  }
 336+  };
 337 }
 338diff --git a/src/core/deterministic.ts b/src/core/deterministic.ts
 339index ae1dadcff4a135053ccf2d098b92cfbd21da1456..6f6616772b15e643860372e7b9fb0c7637c3faa7 100644
 340--- a/src/core/deterministic.ts
 341+++ b/src/core/deterministic.ts
 342@@ -1,4 +1,4 @@
 343-import type { Decision } from "./types"
 344+import type { Decision } from "./types";
 345 import {
 346   HARD_ALLOW_PATTERNS,
 347   CONFIG_ALLOW_PATTERNS,
 348@@ -6,165 +6,233 @@ import {
 349   SHELL_CONTROL_RE,
 350   STDERR_REDIRECT_RE,
 351   DEVNULL_REDIRECT_RE,
 352-} from "./rules"
 353-import { stripRtkPrefix, expandHome } from "./normalize"
 354+} from "./rules";
 355+import { stripRtkPrefix, expandHome } from "./normalize";
 356 
 357 function hasShellControl(cmd: string): boolean {
 358-  return SHELL_CONTROL_RE.test(cmd)
 359+  return SHELL_CONTROL_RE.test(cmd);
 360 }
 361 
 362 function checkHardAllow(command: string): Decision | undefined {
 363-  const cmd = command.trim()
 364-  if (!cmd || hasShellControl(cmd)) return undefined
 365+  const cmd = command.trim();
 366+  if (!cmd || hasShellControl(cmd)) return undefined;
 367   for (const pat of HARD_ALLOW_PATTERNS) {
 368     if (pat.test(cmd)) {
 369       return {
 370         decision: "allow",
 371         reason: `Matched safe pattern: ${pat.source.slice(0, 50)}`,
 372         category: "read_only",
 373-      }
 374+      };
 375     }
 376   }
 377-  return undefined
 378+  return undefined;
 379 }
 380 
 381 function shellWords(command: string): string[] {
 382   return (command.match(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[^\s]+/g) ?? []).map((word) => {
 383     if ((word.startsWith('"') && word.endsWith('"')) || (word.startsWith("'") && word.endsWith("'"))) {
 384-      return word.slice(1, -1)
 385+      return word.slice(1, -1);
 386     }
 387-    return word
 388-  })
 389+    return word;
 390+  });
 391 }
 392 
 393 function isSecretPath(word: string): boolean {
 394-  const path = word.replace(/\/+$/, "")
 395-  if (/(?:^|\/)\.env[A-Za-z0-9._-]*(?:\/|$)/i.test(path)) return true
 396-  if (/(?:^|\/)(?:credentials?|tokens?)(?:[._-][A-Za-z0-9_-]+)*(?:\/|$)/i.test(path)) return true
 397+  const path = word.replace(/\/+$/, "");
 398+  if (/(?:^|\/)\.env[A-Za-z0-9._-]*(?:\/|$)/i.test(path)) return true;
 399+  if (/(?:^|\/)(?:credentials?|tokens?)(?:[._-][A-Za-z0-9_-]+)*(?:\/|$)/i.test(path)) return true;
 400 
 401-  const home = process.env.HOME
 402-  const homePrefix = home ? `(?:~|${home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})` : "~"
 403+  const home = process.env.HOME;
 404+  const homePrefix = home ? `(?:~|${home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})` : "~";
 405   return new RegExp(
 406     `^${homePrefix}/(?:\\.(?:aws|ssh|azure|kube|gnupg|oci)(?:/|$)|\\.config/(?:gcloud|gh|hub|azure|doctl|rclone|sops|containers)(?:/|$)|\\.local/share/keyrings(?:/|$)|\\.docker/config\\.json$|\\.(?:netrc|npmrc|pypirc|git-credentials)$)`,
 407     "i",
 408-  ).test(path)
 409+  ).test(path);
 410 }
 411 
 412 function positionalWords(words: string[], valueOptions: ReadonlySet<string> = new Set()): string[] {
 413-  const positional: string[] = []
 414-  let options = true
 415+  const positional: string[] = [];
 416+  let options = true;
 417   for (let index = 0; index < words.length; index += 1) {
 418-    const word = words[index]
 419+    const word = words[index];
 420     if (options && word === "--") {
 421-      options = false
 422-      continue
 423+      options = false;
 424+      continue;
 425     }
 426     if (options && word.startsWith("-")) {
 427-      if (valueOptions.has(word)) index += 1
 428-      continue
 429+      if (valueOptions.has(word)) index += 1;
 430+      continue;
 431     }
 432-    positional.push(word)
 433+    positional.push(word);
 434   }
 435-  return positional
 436+  return positional;
 437 }
 438 
 439 function hasSecretOptionValue(arguments_: string[], optionNames: ReadonlySet<string>): boolean {
 440   for (let index = 0; index < arguments_.length; index += 1) {
 441-    const word = arguments_[index]
 442diff --git a/src/core/index.ts b/src/core/index.ts
 443index deace70d68115317dbe36f258cc97e81bfe49577..2215d6055fc05215847bc512ef036684e8be4229 100644
 444--- a/src/core/index.ts
 445+++ b/src/core/index.ts
 446@@ -1,10 +1,10 @@
 447-export * from "./audit"
 448-export * from "./cache"
 449-export * from "./config"
 450-export * from "./deterministic"
 451-export * from "./normalize"
 452-export * from "./pipeline"
 453-export * from "./providers"
 454-export * from "./review"
 455-export * from "./rules"
 456-export * from "./types"
 457+export * from "./audit";
 458+export * from "./cache";
 459+export * from "./config";
 460+export * from "./deterministic";
 461+export * from "./normalize";
 462+export * from "./pipeline";
 463+export * from "./providers";
 464+export * from "./review";
 465+export * from "./rules";
 466+export * from "./types";
 467diff --git a/src/core/normalize.ts b/src/core/normalize.ts
 468index e075b3413b92a34d70572d7c24230ee238388085..adb10b497067578f98038613766115bcf0081be6 100644
 469--- a/src/core/normalize.ts
 470+++ b/src/core/normalize.ts
 471@@ -1,89 +1,92 @@
 472-import { createHash } from "node:crypto"
 473-import { POLICY_VERSION } from "./rules"
 474-import type { PolicyRequest } from "./types"
 475+import { createHash } from "node:crypto";
 476+import { POLICY_VERSION } from "./rules";
 477+import type { PolicyRequest } from "./types";
 478 
 479-const RTK_PREFIX_RE = /^rtk\s+/
 480-const SENSITIVE_KEY_RE = /(?:api[-_]?key|auth(?:entication|orization)?|credential|cookie|password|secret|token)/i
 481-const SENSITIVE_ARGUMENT_RE = /((?:^|\s)(?:-u|-H)(?:=|\s*)|(?:^|\s)--(?:api[-_]?key|authorization|cookie|password|secret|token|user|proxy-user|header)(?:=|\s+))(?:(?:"(?:[^"\\]|\\.)*")|(?:'(?:[^'\\]|\\.)*')|\S+)/gi
 482-const URL_USERINFO_RE = /([a-z][a-z\d+.-]*:\/\/)[^/\s@]+@/gi
 483-const SENSITIVE_QUERY_RE = /([?&](?:api[-_]?key|authorization|credential|cookie|password|secret|token)=)[^&\s]*/gi
 484+const RTK_PREFIX_RE = /^rtk\s+/;
 485+const SENSITIVE_KEY_RE = /(?:api[-_]?key|auth(?:entication|orization)?|credential|cookie|password|secret|token)/i;
 486+const SENSITIVE_ARGUMENT_RE =
 487+  /((?:^|\s)(?:-u|-H)(?:=|\s*)|(?:^|\s)--(?:api[-_]?key|authorization|cookie|password|secret|token|user|proxy-user|header)(?:=|\s+))(?:(?:"(?:[^"\\]|\\.)*")|(?:'(?:[^'\\]|\\.)*')|\S+)/gi;
 488+const URL_USERINFO_RE = /([a-z][a-z\d+.-]*:\/\/)[^/\s@]+@/gi;
 489+const SENSITIVE_QUERY_RE = /([?&](?:api[-_]?key|authorization|credential|cookie|password|secret|token)=)[^&\s]*/gi;
 490 
 491 export function stripRtkPrefix(command: string): string {
 492-  return command.replace(RTK_PREFIX_RE, "")
 493+  return command.replace(RTK_PREFIX_RE, "");
 494 }
 495 
 496 export function expandHome(command: string): string {
 497-  const home = process.env.HOME
 498-  if (!home) return command
 499+  const home = process.env.HOME;
 500+  if (!home) return command;
 501   const unwrapped = command.replace(
 502     /(["'])\$HOME((?:[^"'\\]|\\.)*)\1/g,
 503     (_, _quote: string, rest: string) => home + rest,
 504-  )
 505-  return unwrapped.replaceAll("$HOME", home)
 506+  );
 507+  return unwrapped.replaceAll("$HOME", home);
 508 }
 509 
 510 function normalizeBashCommand(command: string): string {
 511-  let normalized = command.split(/\s+/).join(" ").trim()
 512-  const home = process.env.HOME ?? "~"
 513-  normalized = normalized.replaceAll("~", home)
 514-  normalized = expandHome(normalized)
 515-  normalized = normalized.replace(/;+\s*$/, "").trim()
 516-  return stripRtkPrefix(normalized)
 517+  let normalized = command.split(/\s+/).join(" ").trim();
 518+  const home = process.env.HOME ?? "~";
 519+  normalized = normalized.replaceAll("~", home);
 520+  normalized = expandHome(normalized);
 521+  normalized = normalized.replace(/;+\s*$/, "").trim();
 522+  return stripRtkPrefix(normalized);
 523 }
 524 
 525 function stableJson(value: unknown): string {
 526-  if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`
 527+  if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
 528   if (value && typeof value === "object") {
 529     const entries = Object.entries(value as Record<string, unknown>)
 530       .sort(([left], [right]) => left.localeCompare(right))
 531-      .map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
 532-    return `{${entries.join(",")}}`
 533+      .map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`);
 534+    return `{${entries.join(",")}}`;
 535   }
 536-  return JSON.stringify(value)
 537+  return JSON.stringify(value);
 538 }
 539 
 540 export function normalizeRequest(toolName: string, input: Record<string, unknown>): string {
 541   switch (toolName) {
 542     case "bash": {
 543-      const command = typeof input.command === "string" ? input.command : ""
 544-      return `Bash:${normalizeBashCommand(command)}`
 545+      const command = typeof input.command === "string" ? input.command : "";
 546+      return `Bash:${normalizeBashCommand(command)}`;
 547     }
 548     case "webfetch":
 549-      return `WebFetch:${input.url ?? ""}`
 550+      return `WebFetch:${input.url ?? ""}`;
 551     default:
 552-      return `${toolName}:${stableJson(input)}`
 553+      return `${toolName}:${stableJson(input)}`;
 554   }
 555 }
 556 
 557 export function cacheKey(normalized: string): string {
 558-  const payload = `${POLICY_VERSION}\n${normalized}`
 559-  return createHash("sha256").update(payload).digest("hex").slice(0, 16)
 560+  const payload = `${POLICY_VERSION}\n${normalized}`;
 561+  return createHash("sha256").update(payload).digest("hex").slice(0, 16);
 562 }
 563 
 564 function redactText(value: string): string {
 565   return value
 566     .replace(SENSITIVE_ARGUMENT_RE, "$1[REDACTED]")
 567     .replace(URL_USERINFO_RE, "$1[REDACTED]@")
 568-    .replace(SENSITIVE_QUERY_RE, "$1[REDACTED]")
 569+    .replace(SENSITIVE_QUERY_RE, "$1[REDACTED]");
 570 }
 571diff --git a/src/core/pipeline.ts b/src/core/pipeline.ts
 572index eb753e9eb56fe6ab26ca19dfd24505d4a7c5a2d5..dbd45fa5643f8cf0dd4dd9a458542a5d9a7603e6 100644
 573--- a/src/core/pipeline.ts
 574+++ b/src/core/pipeline.ts
 575@@ -8,30 +8,30 @@ import type {
 576   PolicyPrecheck,
 577   PolicyRequest,
 578   PolicyReviewer,
 579-} from "./types"
 580+} from "./types";
 581 
 582 export type PolicyPipelineOptions = {
 583-  prechecks?: PolicyPrecheck[]
 584-  deterministic(request: PolicyRequest, context: PolicyContext): Decision | undefined
 585-  cache: DecisionCache
 586-  audit: DecisionAudit
 587-  reviewer: PolicyReviewer
 588-  normalize(request: PolicyRequest, context: PolicyContext): string
 589-  cacheKey(normalized: string): string
 590-  inputSummary(request: PolicyRequest): string
 591-}
 592+  prechecks?: PolicyPrecheck[];
 593+  deterministic(request: PolicyRequest, context: PolicyContext): Decision | undefined;
 594+  cache: DecisionCache;
 595+  audit: DecisionAudit;
 596+  reviewer: PolicyReviewer;
 597+  normalize(request: PolicyRequest, context: PolicyContext): string;
 598+  cacheKey(normalized: string): string;
 599+  inputSummary(request: PolicyRequest): string;
 600+};
 601 
 602 type EvaluationOptions = {
 603-  skipPrechecks?: boolean
 604-  skipDeterministic?: boolean
 605-  skipLLMReview?: boolean
 606-}
 607+  skipPrechecks?: boolean;
 608+  skipDeterministic?: boolean;
 609+  skipLLMReview?: boolean;
 610+};
 611 
 612 const EMPTY_DECISION: Decision = {
 613   decision: "allow",
 614   reason: "No operations to evaluate",
 615   category: "empty",
 616-}
 617+};
 618 
 619 export function createPolicyPipeline(options: PolicyPipelineOptions) {
 620   async function record(
 621@@ -48,11 +48,10 @@ export function createPolicyPipeline(options: PolicyPipelineOptions) {
 622       timingMs: performance.now() - startedAt,
 623       sessionId: context.sessionId,
 624       rawResponse: result.error ? result.rawResponse : undefined,
 625-    }
 626+    };
 627     try {
 628-      await options.audit.record(entry)
 629-    } catch {
 630-    }
 631+      await options.audit.record(entry);
 632+    } catch {}
 633   }
 634 
 635   async function complete(
 636@@ -61,17 +60,17 @@ export function createPolicyPipeline(options: PolicyPipelineOptions) {
 637     result: PolicyEvaluation,
 638     startedAt: number,
 639   ): Promise<PolicyEvaluation> {
 640-    await record(request, context, result, startedAt)
 641-    return result
 642+    await record(request, context, result, startedAt);
 643+    return result;
 644   }
 645 
 646   async function precheck(request: PolicyRequest, context: PolicyContext): Promise<PolicyEvaluation | undefined> {
 647-    const startedAt = performance.now()
 648+    const startedAt = performance.now();
 649     for (const check of options.prechecks ?? []) {
 650-      const decision = await check.decide(request, context)
 651-      if (decision) return complete(request, context, { decision, source: check.source }, startedAt)
 652+      const decision = await check.decide(request, context);
 653+      if (decision) return complete(request, context, { decision, source: check.source }, startedAt);
 654     }
 655-    return undefined
 656+    return undefined;
 657   }
 658 
 659   async function evaluate(
 660@@ -80,44 +79,48 @@ export function createPolicyPipeline(options: PolicyPipelineOptions) {
 661     evaluationOptions: EvaluationOptions = {},
 662   ): Promise<PolicyEvaluation> {
 663     if (!evaluationOptions.skipPrechecks) {
 664-      const checked = await precheck(request, context)
 665-      if (checked) return checked
 666+      const checked = await precheck(request, context);
 667+      if (checked) return checked;
 668     }
 669 
 670-    const startedAt = performance.now()
 671+    const startedAt = performance.now();
 672     if (!evaluationOptions.skipDeterministic) {
 673-      const deterministic = options.deterministic(request, context)
 674+      const deterministic = options.deterministic(request, context);
 675diff --git a/src/core/providers.ts b/src/core/providers.ts
 676index ee15e1869182af691db86cd809b4f55818f6b5e6..9e14f44413f22e44bdbc176e23c08e7c1bb70b23 100644
 677--- a/src/core/providers.ts
 678+++ b/src/core/providers.ts
 679@@ -1,27 +1,27 @@
 680-import type { ChatMessage } from "./review"
 681-import type { ReviewerConfig } from "./types"
 682+import type { ChatMessage } from "./review";
 683+import type { ReviewerConfig } from "./types";
 684 
 685 type LlamaResponse = {
 686   choices?: {
 687     message?: {
 688-      content?: unknown
 689-    }
 690-  }[]
 691-}
 692+      content?: unknown;
 693+    };
 694+  }[];
 695+};
 696 
 697 function textValue(value: unknown): string | undefined {
 698-  return typeof value === "string" && value.trim() ? value : undefined
 699+  return typeof value === "string" && value.trim() ? value : undefined;
 700 }
 701 
 702 function completionUrl(baseUrl: string): string {
 703-  const normalized = baseUrl.replace(/\/+$/, "")
 704-  const root = normalized.endsWith("/v1") ? normalized : `${normalized}/v1`
 705-  return `${root}/chat/completions`
 706+  const normalized = baseUrl.replace(/\/+$/, "");
 707+  const root = normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
 708+  return `${root}/chat/completions`;
 709 }
 710 
 711 async function responseError(response: Response): Promise<never> {
 712-  const detail = (await response.text()).trim().slice(0, 1000)
 713-  throw new Error(`llama-server API ${response.status}${detail ? `: ${detail}` : ""}`)
 714+  const detail = (await response.text()).trim().slice(0, 1000);
 715+  throw new Error(`llama-server API ${response.status}${detail ? `: ${detail}` : ""}`);
 716 }
 717 
 718 export async function callLlamaReviewer(
 719@@ -42,10 +42,10 @@ export async function callLlamaReviewer(
 720       chat_template_kwargs: { enable_thinking: config.enableThinking },
 721       reasoning_format: "deepseek",
 722     }),
 723-  })
 724-  if (!response.ok) return responseError(response)
 725-  const data = await response.json() as LlamaResponse
 726-  const text = textValue(data.choices?.[0]?.message?.content)
 727-  if (!text) throw new Error("llama-server response had no generated text")
 728-  return text
 729+  });
 730+  if (!response.ok) return responseError(response);
 731+  const data = (await response.json()) as LlamaResponse;
 732+  const text = textValue(data.choices?.[0]?.message?.content);
 733+  if (!text) throw new Error("llama-server response had no generated text");
 734+  return text;
 735 }
 736diff --git a/src/core/review.ts b/src/core/review.ts
 737index b3261fafc4db3cfca84028a8986b7f6e28b12118..a66b6335d6f31834c54aaf066a86c3a80884e872 100644
 738--- a/src/core/review.ts
 739+++ b/src/core/review.ts
 740@@ -1,5 +1,5 @@
 741-import { callLlamaReviewer } from "./providers"
 742-import { llmPolicyPrompt } from "./rules"
 743+import { callLlamaReviewer } from "./providers";
 744+import { llmPolicyPrompt } from "./rules";
 745 import {
 746   isDecisionCategory,
 747   type Decision,
 748@@ -8,55 +8,57 @@ import {
 749   type PolicyRequest,
 750   type PolicyReviewer,
 751   type ReviewerConfig,
 752-} from "./types"
 753+} from "./types";
 754 
 755 export type ChatMessage = {
 756-  role: "system" | "user"
 757-  content: string
 758-}
 759+  role: "system" | "user";
 760+  content: string;
 761+};
 762 
 763 export type PiReviewerCaller = (
 764   config: Extract<ReviewerConfig, { kind: "pi" }>,
 765   messages: ChatMessage[],
 766   maxTokens: number,
 767   context: PolicyContext,
 768-) => Promise<string>
 769+) => Promise<string>;
 770 
 771 const ASK_FALLBACK: Decision = {
 772   decision: "ask",
 773   reason: "Policy engine error",
 774   category: "uncertain",
 775-}
 776+};
 777 
 778 export function parseLLMResponse(content: string): Decision | undefined {
 779   try {
 780-    const trimmed = content.trim()
 781-    const json = trimmed.startsWith("{")
 782-      ? trimmed
 783-      : trimmed.slice(trimmed.indexOf("{"), trimmed.lastIndexOf("}") + 1)
 784-    const data = JSON.parse(json) as Record<string, unknown>
 785-    const decision = data.decision
 786-    if (decision !== "allow" && decision !== "deny" && decision !== "ask") return undefined
 787+    const trimmed = content.trim();
 788+    const json = trimmed.startsWith("{") ? trimmed : trimmed.slice(trimmed.indexOf("{"), trimmed.lastIndexOf("}") + 1);
 789+    const data = JSON.parse(json) as Record<string, unknown>;
 790+    const decision = data.decision;
 791+    if (decision !== "allow" && decision !== "deny" && decision !== "ask") return undefined;
 792     return {
 793       decision,
 794       reason: typeof data.reason === "string" ? data.reason : "No reason provided",
 795       category: isDecisionCategory(data.category) ? data.category : "uncertain",
 796-    }
 797+    };
 798   } catch {
 799-    return undefined
 800+    return undefined;
 801   }
 802 }
 803 
 804 function errorReason(error: unknown): string {
 805-  return `Policy engine error: ${error instanceof Error ? error.message : String(error)}`
 806+  return `Policy engine error: ${error instanceof Error ? error.message : String(error)}`;
 807 }
 808 
 809 function reviewRequest(request: PolicyRequest, context: PolicyContext): string {
 810-  return `<tool_request>\n${JSON.stringify({
 811-    cwd: context.cwd,
 812-    toolName: request.toolName,
 813-    input: request.input,
 814-  }, null, 2)}\n</tool_request>`
 815+  return `<tool_request>\n${JSON.stringify(
 816+    {
 817+      cwd: context.cwd,
 818+      toolName: request.toolName,
 819+      input: request.input,
 820+    },
 821+    null,
 822+    2,
 823+  )}\n</tool_request>`;
 824 }
 825 
 826 export function createPolicyReviewer(
 827@@ -70,24 +72,25 @@ export function createPolicyReviewer(
 828         const messages = [
 829           { role: "system" as const, content: llmPolicyPrompt(externalDirectories) },
 830           { role: "user" as const, content: reviewRequest(request, context) },
 831-        ]
 832-        const rawResponse = config.kind === "pi"
 833-          ? await callPiReviewer(config, messages, 512, context)
 834-          : await callLlamaReviewer(config, messages, 1024)
 835-        const decision = parseLLMResponse(rawResponse)
 836-        if (decision) return { decision, rawResponse }
 837+        ];
 838+        const rawResponse =
 839+          config.kind === "pi"
 840diff --git a/src/core/rules.ts b/src/core/rules.ts
 841index 10a564fd69eebc02a9fe693f73eeaf816db290ed..922dc10b03bcd20de191938cf86f959bc2faf2b1 100644
 842--- a/src/core/rules.ts
 843+++ b/src/core/rules.ts
 844@@ -227,19 +227,12 @@ Respond with ONLY this JSON, WITHOUT markdown formatting, otherwise this program
 845   "category": ${DECISION_CATEGORIES.map((category) => `"${category}"`).join(" | ")}
 846 }`;
 847 
 848-export function llmPolicyPrompt(
 849-  externalDirectories: readonly string[] = [],
 850-): string {
 851+export function llmPolicyPrompt(externalDirectories: readonly string[] = []): string {
 852   const directories =
 853     externalDirectories.length === 0
 854       ? "  - No additional directories."
 855-      : `${externalDirectories
 856-          .map((pattern) => `  - ${JSON.stringify(pattern)}`)
 857-          .join("\n")}
 858+      : `${externalDirectories.map((pattern) => `  - ${JSON.stringify(pattern)}`).join("\n")}
 859 
 860 Treat these as trusted policy rules. Allow access when a tool-call path starts with a listed path. This overrides the general directory guidance above.`;
 861-  return LLM_POLICY_PROMPT.replace(
 862-    EXTERNAL_DIRECTORIES_PLACEHOLDER,
 863-    directories,
 864-  );
 865+  return LLM_POLICY_PROMPT.replace(EXTERNAL_DIRECTORIES_PLACEHOLDER, directories);
 866 }
 867diff --git a/src/core/types.ts b/src/core/types.ts
 868index c0767d0178e75f99f85bf3e9f890c72bcd054ebf..49b4b1286a8cf6bd80f1719be72afe9e799ef24e 100644
 869--- a/src/core/types.ts
 870+++ b/src/core/types.ts
 871@@ -21,98 +21,98 @@ export const DECISION_CATEGORIES = [
 872   "grep",
 873   "ls",
 874   "webfetch",
 875-] as const
 876+] as const;
 877 
 878-export type DecisionCategory = (typeof DECISION_CATEGORIES)[number]
 879+export type DecisionCategory = (typeof DECISION_CATEGORIES)[number];
 880 
 881-const decisionCategorySet: ReadonlySet<string> = new Set(DECISION_CATEGORIES)
 882+const decisionCategorySet: ReadonlySet<string> = new Set(DECISION_CATEGORIES);
 883 
 884 export function isDecisionCategory(value: unknown): value is DecisionCategory {
 885-  return typeof value === "string" && decisionCategorySet.has(value)
 886+  return typeof value === "string" && decisionCategorySet.has(value);
 887 }
 888 
 889 export type Decision = {
 890-  decision: "allow" | "deny" | "ask"
 891-  reason: string
 892-  category: DecisionCategory
 893-}
 894+  decision: "allow" | "deny" | "ask";
 895+  reason: string;
 896+  category: DecisionCategory;
 897+};
 898 
 899 export type LLMEvaluationResult = {
 900-  decision: Decision
 901-  rawResponse?: string
 902-  error?: string
 903-}
 904+  decision: Decision;
 905+  rawResponse?: string;
 906+  error?: string;
 907+};
 908 
 909 export type PolicyReviewer = {
 910-  evaluate(request: PolicyRequest, context: PolicyContext): Promise<LLMEvaluationResult>
 911-}
 912+  evaluate(request: PolicyRequest, context: PolicyContext): Promise<LLMEvaluationResult>;
 913+};
 914 
 915-export type PiReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
 916+export type PiReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
 917 
 918 export type ReviewerConfig =
 919   | {
 920-    kind: "pi"
 921-    provider: string
 922-    model: string
 923-    reasoningEffort?: PiReasoningEffort
 924-    promptCacheKey?: string
 925-  }
 926+      kind: "pi";
 927+      provider: string;
 928+      model: string;
 929+      reasoningEffort?: PiReasoningEffort;
 930+      promptCacheKey?: string;
 931+    }
 932   | {
 933-    kind: "llama.cpp"
 934-    baseUrl: string
 935-    model: string
 936-    enableThinking: boolean
 937-  }
 938+      kind: "llama.cpp";
 939+      baseUrl: string;
 940+      model: string;
 941+      enableThinking: boolean;
 942+    };
 943 
 944 export type CacheEntry = {
 945-  key: string
 946-  toolName: string
 947-  decision: Decision
 948-  source: "llm"
 949-  createdAt: string
 950-}
 951+  key: string;
 952+  toolName: string;
 953+  decision: Decision;
 954+  source: "llm";
 955+  createdAt: string;
 956+};
 957 
 958 export type DecisionCache = {
 959-  lookup(key: string): Promise<Decision | undefined>
 960-  write(entry: CacheEntry): Promise<void>
 961-}
 962+  lookup(key: string): Promise<Decision | undefined>;
 963+  write(entry: CacheEntry): Promise<void>;
 964+};
 965 
 966-export type DecisionSource = "session" | "static" | "deterministic" | "cache" | "llm"
 967+export type DecisionSource = "session" | "static" | "deterministic" | "cache" | "llm";
 968 
 969 export type AuditEntry = {
 970-  toolName: string
 971diff --git a/src/index.ts b/src/index.ts
 972index fc3a91016b51c12c495f6257a263520cea311c45..178dfe9160bd85851f00bcd997f3a2faba7c9044 100644
 973--- a/src/index.ts
 974+++ b/src/index.ts
 975@@ -1 +1 @@
 976-export { default } from "./pi/index"
 977+export { default } from "./pi/index";
 978diff --git a/src/pi/bash-split.ts b/src/pi/bash-split.ts
 979index c4571d102af9349be2eb5a2ad7474c19140352f5..dff2c9e7733dfbfb9207f8a9e2d884014dc0f78d 100644
 980--- a/src/pi/bash-split.ts
 981+++ b/src/pi/bash-split.ts
 982@@ -1,41 +1,41 @@
 983-import { createRequire } from "node:module"
 984-import { Language, Parser, type Node } from "web-tree-sitter"
 985+import { createRequire } from "node:module";
 986+import { Language, Parser, type Node } from "web-tree-sitter";
 987 
 988 export type BashSplitResult = {
 989-  commands: string[]
 990-  parsed: boolean
 991-}
 992+  commands: string[];
 993+  parsed: boolean;
 994+};
 995 
 996 type BashParser = {
 997-  parse(command: string): { rootNode: Node; delete(): void } | null
 998-}
 999+  parse(command: string): { rootNode: Node; delete(): void } | null;
1000+};
1001 
1002-type BashParserLoader = () => Promise<BashParser>
1003+type BashParserLoader = () => Promise<BashParser>;
1004 
1005-const require = createRequire(import.meta.url)
1006-let parserPromise: Promise<BashParser> | undefined
1007+const require = createRequire(import.meta.url);
1008+let parserPromise: Promise<BashParser> | undefined;
1009 
1010 function source(node: Node): string {
1011-  return (node.parent?.type === "redirected_statement" ? node.parent.text : node.text).trim()
1012+  return (node.parent?.type === "redirected_statement" ? node.parent.text : node.text).trim();
1013 }
1014 
1015 async function loadBashParser(): Promise<BashParser> {
1016   if (!parserPromise) {
1017     parserPromise = (async () => {
1018-      const parserWasm = require.resolve("web-tree-sitter/web-tree-sitter.wasm")
1019-      const bashWasm = require.resolve("tree-sitter-bash/tree-sitter-bash.wasm")
1020-      await Parser.init({ locateFile: () => parserWasm })
1021-      const language = await Language.load(bashWasm)
1022-      const parser = new Parser()
1023-      parser.setLanguage(language)
1024-      return parser
1025-    })()
1026+      const parserWasm = require.resolve("web-tree-sitter/web-tree-sitter.wasm");
1027+      const bashWasm = require.resolve("tree-sitter-bash/tree-sitter-bash.wasm");
1028+      await Parser.init({ locateFile: () => parserWasm });
1029+      const language = await Language.load(bashWasm);
1030+      const parser = new Parser();
1031+      parser.setLanguage(language);
1032+      return parser;
1033+    })();
1034   }
1035-  return parserPromise
1036+  return parserPromise;
1037 }
1038 
1039 function raw(command: string): BashSplitResult {
1040-  return { commands: [command], parsed: false }
1041+  return { commands: [command], parsed: false };
1042 }
1043 
1044 export async function splitBashCommand(
1045@@ -43,25 +43,25 @@ export async function splitBashCommand(
1046   loadParser: BashParserLoader = loadBashParser,
1047 ): Promise<BashSplitResult> {
1048   try {
1049-    const parser = await loadParser()
1050-    const tree = parser.parse(command)
1051-    if (!tree || tree.rootNode.hasError) return raw(command)
1052+    const parser = await loadParser();
1053+    const tree = parser.parse(command);
1054+    if (!tree || tree.rootNode.hasError) return raw(command);
1055     try {
1056-      const commands: string[] = []
1057-      const seen = new Set<string>()
1058+      const commands: string[] = [];
1059+      const seen = new Set<string>();
1060       for (const node of tree.rootNode.descendantsOfType("command")) {
1061-        if (!node) continue
1062-        const text = source(node)
1063+        if (!node) continue;
1064+        const text = source(node);
1065         if (text && !seen.has(text)) {
1066-          seen.add(text)
1067-          commands.push(text)
1068+          seen.add(text);
1069+          commands.push(text);
1070         }
1071       }
1072-      return { commands, parsed: true }
1073+      return { commands, parsed: true };
1074     } finally {
1075-      tree.delete()
1076+      tree.delete();
1077     }
1078   } catch {
1079-    return raw(command)
1080+    return raw(command);
1081   }
1082diff --git a/src/pi/config.ts b/src/pi/config.ts
1083index c8c746b405bf1250815b2f4ea0ac3128047d0877..ada564c0862437dc4378276d0517941ee3d2aa43 100644
1084--- a/src/pi/config.ts
1085+++ b/src/pi/config.ts
1086@@ -1,114 +1,109 @@
1087-import { existsSync, readFileSync } from "node:fs"
1088-import { homedir } from "node:os"
1089-import { join } from "node:path"
1090-import { parseReviewerConfig } from "../core/config"
1091-import type { ReviewerConfig } from "../core/types"
1092+import { existsSync, readFileSync } from "node:fs";
1093+import { homedir } from "node:os";
1094+import { join } from "node:path";
1095+import { parseReviewerConfig } from "../core/config";
1096+import type { ReviewerConfig } from "../core/types";
1097 
1098-export type PermissionAction = "allow" | "check" | "deny"
1099-export type ExternalDirectories = string[]
1100+export type PermissionAction = "allow" | "check" | "deny";
1101+export type ExternalDirectories = string[];
1102 
1103 export type PiPolicyEngineConfig = {
1104-  reviewer: ReviewerConfig
1105-  tools: Record<string, PermissionAction>
1106-  externalDirectories: ExternalDirectories
1107-}
1108+  reviewer: ReviewerConfig;
1109+  tools: Record<string, PermissionAction>;
1110+  externalDirectories: ExternalDirectories;
1111+};
1112 
1113 function globalConfigPath(): string {
1114-  return join(process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent"), "policy-engine.json")
1115+  return join(process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent"), "policy-engine.json");
1116 }
1117 
1118 function configPaths(cwd: string): string[] {
1119-  return [globalConfigPath(), join(cwd, ".pi", "policy-engine.json")]
1120+  return [globalConfigPath(), join(cwd, ".pi", "policy-engine.json")];
1121 }
1122 
1123 export function piPolicyPaths(): { cacheFile: string; auditFile: string } {
1124-  const directory = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent")
1125+  const directory = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
1126   return {
1127     cacheFile: join(directory, "policy-engine", "cache", "decisions.jsonl"),
1128     auditFile: join(directory, "policy-engine", "logs", "policy.jsonl"),
1129-  }
1130+  };
1131 }
1132 
1133 function objectValue(value: unknown): Record<string, unknown> | undefined {
1134-  return value && typeof value === "object" && !Array.isArray(value)
1135-    ? value as Record<string, unknown>
1136-    : undefined
1137+  return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
1138 }
1139 
1140 function permissionAction(value: unknown, field: string): PermissionAction {
1141-  if (value === "allow" || value === "check" || value === "deny") return value
1142-  throw new Error(`${field} must be "allow", "check", or "deny"`)
1143+  if (value === "allow" || value === "check" || value === "deny") return value;
1144+  throw new Error(`${field} must be "allow", "check", or "deny"`);
1145 }
1146 
1147 function toolPermissions(value: unknown): Record<string, PermissionAction> {
1148-  const data = objectValue(value)
1149-  if (!data) throw new Error("tools must be a JSON object")
1150+  const data = objectValue(value);
1151+  if (!data) throw new Error("tools must be a JSON object");
1152   return Object.fromEntries(
1153     Object.entries(data).map(([name, action]) => [name, permissionAction(action, `tools.${name}`)]),
1154-  )
1155+  );
1156 }
1157 
1158 function externalDirectories(value: unknown): ExternalDirectories {
1159   if (!Array.isArray(value) || !value.every((path) => typeof path === "string")) {
1160-    throw new Error("externalDirectories must be a JSON array of strings")
1161+    throw new Error("externalDirectories must be a JSON array of strings");
1162   }
1163-  return value
1164+  return value;
1165 }
1166 
1167 export function parsePiPolicyConfig(value: unknown): PiPolicyEngineConfig {
1168-  const data = objectValue(value)
1169-  if (!data) throw new Error("policy engine configuration must be a JSON object")
1170+  const data = objectValue(value);
1171+  if (!data) throw new Error("policy engine configuration must be a JSON object");
1172   if (data.permission !== undefined) {
1173-    throw new Error("permission has been renamed to tools; external_directory has been renamed to externalDirectories")
1174+    throw new Error("permission has been renamed to tools; external_directory has been renamed to externalDirectories");
1175   }
1176   return {
1177     reviewer: parseReviewerConfig(data),
1178     tools: data.tools === undefined ? {} : toolPermissions(data.tools),
1179     externalDirectories: data.externalDirectories === undefined ? [] : externalDirectories(data.externalDirectories),
1180-  }
1181+  };
1182 }
1183 
1184 function mergeObjects(
1185   base: Record<string, unknown> | undefined,
1186diff --git a/src/pi/index.ts b/src/pi/index.ts
1187index 576f69b4c4e4fa545db36ec39be5b7c8b84cc434..a1c3450e103cecfd0db861637adf7162e71f789a 100644
1188--- a/src/pi/index.ts
1189+++ b/src/pi/index.ts
1190@@ -1,78 +1,72 @@
1191-import type { UserMessage } from "@earendil-works/pi-ai"
1192-import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"
1193-import { createJsonlDecisionAudit } from "../core/audit"
1194-import { createJsonlDecisionCache } from "../core/cache"
1195-import { checkDeterministic } from "../core/deterministic"
1196-import { auditInputSummary, cacheKey, normalizeRequest } from "../core/normalize"
1197-import { createPolicyPipeline } from "../core/pipeline"
1198-import { createPolicyReviewer, type PiReviewerCaller } from "../core/review"
1199-import type { PolicyContext, PolicyEvaluation, PolicyRequest } from "../core/types"
1200-import { splitBashCommand, type BashSplitResult } from "./bash-split"
1201-import { loadPiPolicyConfig, piPolicyPaths } from "./config"
1202-import { PolicyApprovalDialog } from "./policy-approval"
1203+import type { UserMessage } from "@earendil-works/pi-ai";
1204+import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
1205+import { createJsonlDecisionAudit } from "../core/audit";
1206+import { createJsonlDecisionCache } from "../core/cache";
1207+import { checkDeterministic } from "../core/deterministic";
1208+import { auditInputSummary, cacheKey, normalizeRequest } from "../core/normalize";
1209+import { createPolicyPipeline } from "../core/pipeline";
1210+import { createPolicyReviewer, type PiReviewerCaller } from "../core/review";
1211+import type { PolicyContext, PolicyEvaluation, PolicyRequest } from "../core/types";
1212+import { splitBashCommand, type BashSplitResult } from "./bash-split";
1213+import { loadPiPolicyConfig, piPolicyPaths } from "./config";
1214+import { PolicyApprovalDialog } from "./policy-approval";
1215 import {
1216   createSessionBashAllowOverride,
1217   matchesSessionBashAllowOverride,
1218   restoreSessionBashAllowOverride,
1219   SESSION_BASH_ALLOW_ENTRY,
1220   type SessionBashAllowOverride,
1221-} from "./session-override"
1222-import { checkStaticPermission } from "./static-permissions"
1223+} from "./session-override";
1224+import { checkStaticPermission } from "./static-permissions";
1225 
1226-type ToolInput = Record<string, unknown>
1227+type ToolInput = Record<string, unknown>;
1228 
1229 function toolInput(input: unknown): ToolInput {
1230-  return input && typeof input === "object" && !Array.isArray(input)
1231-    ? { ...input as ToolInput }
1232-    : {}
1233+  return input && typeof input === "object" && !Array.isArray(input) ? { ...(input as ToolInput) } : {};
1234 }
1235 
1236 function inputSummary(toolName: string, input: ToolInput): string {
1237-  if (toolName === "bash" && typeof input.command === "string") return input.command
1238-  return JSON.stringify(input).slice(0, 500)
1239+  if (toolName === "bash" && typeof input.command === "string") return input.command;
1240+  return JSON.stringify(input).slice(0, 500);
1241 }
1242 
1243 function approvalMessage(toolName: string, input: ToolInput, result: PolicyEvaluation): string {
1244-  const rawResponse = result.error === "Failed to parse response JSON" && result.rawResponse
1245-    ? `\n\nLLM response:\n${result.rawResponse}`
1246-    : ""
1247+  const rawResponse =
1248+    result.error === "Failed to parse response JSON" && result.rawResponse
1249+      ? `\n\nLLM response:\n${result.rawResponse}`
1250+      : "";
1251   if (toolName !== "bash" || typeof input.command !== "string") {
1252-    return `${toolName}: ${inputSummary(toolName, input)}\n\n${result.decision.reason}${rawResponse}`
1253+    return `${toolName}: ${inputSummary(toolName, input)}\n\n${result.decision.reason}${rawResponse}`;
1254   }
1255 
1256   const commands = result.approvalRequests
1257     ?.map((request) => request.input.command)
1258-    .filter((command): command is string => typeof command === "string")
1259-    ?? [input.command]
1260-  return `Entire Bash input:\n${input.command}\n\nCommands requiring review:\n${commands.map((command) => `- ${command}`).join("\n")}\n\n${result.decision.reason}${rawResponse}`
1261-}
1262-
1263-function bypassesPiPolicy(toolName: string): boolean {
1264-  return toolName === "mcp" || toolName.startsWith("mcp__")
1265+    .filter((command): command is string => typeof command === "string") ?? [input.command];
1266+  return `Entire Bash input:\n${input.command}\n\nCommands requiring review:\n${commands.map((command) => `- ${command}`).join("\n")}\n\n${result.decision.reason}${rawResponse}`;
1267 }
1268 
1269 function piReviewerCaller(ctx: ExtensionContext): PiReviewerCaller {
1270   return async (
1271     config: {
1272-      provider: string
1273-      model: string
1274-      reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
1275-      promptCacheKey?: string
1276+      provider: string;
1277+      model: string;
1278+      reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
1279+      promptCacheKey?: string;
1280     },
1281     messages: readonly { role: "system" | "user"; content: string }[],
1282     _maxTokens: number,
1283     context: PolicyContext,
1284   ): Promise<string> => {
1285-    const model = ctx.modelRegistry.find(config.provider, config.model)
1286-    if (!model) throw new Error(`Pi model is unavailable: ${config.provider}/${config.model}`)
1287-    const systemPrompt = messages.find((message) => message.role === "system")?.content
1288+    const model = ctx.modelRegistry.find(config.provider, config.model);
1289+    if (!model) throw new Error(`Pi model is unavailable: ${config.provider}/${config.model}`);
1290diff --git a/src/pi/policy-approval.ts b/src/pi/policy-approval.ts
1291index 85c8f05c8e60bdd4427e1d9d03cdd375fc0260ea..956d166f031d8c013f8499b4f3c8a1b8ddcda80f 100644
1292--- a/src/pi/policy-approval.ts
1293+++ b/src/pi/policy-approval.ts
1294@@ -1,23 +1,25 @@
1295-import type { KeybindingsManager, Theme } from "@earendil-works/pi-coding-agent"
1296-import { Key, matchesKey, truncateToWidth, type TUI, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui"
1297+import type { KeybindingsManager, Theme } from "@earendil-works/pi-coding-agent";
1298+import { Key, matchesKey, truncateToWidth, type TUI, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
1299 
1300 function escapeTerminalControls(value: string): string {
1301-  return [...value].map((character) => {
1302-    if (character === "\n") return character
1303-    const codePoint = character.codePointAt(0)!
1304-    if (codePoint < 0x20 || (codePoint >= 0x7F && codePoint < 0xA0)) {
1305-      return `\\x${codePoint.toString(16).padStart(2, "0")}`
1306-    }
1307-    return character
1308-  }).join("")
1309+  return [...value]
1310+    .map((character) => {
1311+      if (character === "\n") return character;
1312+      const codePoint = character.codePointAt(0)!;
1313+      if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint < 0xa0)) {
1314+        return `\\x${codePoint.toString(16).padStart(2, "0")}`;
1315+      }
1316+      return character;
1317+    })
1318+    .join("");
1319 }
1320 
1321 export class PolicyApprovalDialog {
1322-  private selectedApproval = true
1323-  private scrollTop = 0
1324-  private visibleBodyLines = 1
1325-  private bodyLines: string[] = []
1326-  private readonly message: string
1327+  private selectedApproval = true;
1328+  private scrollTop = 0;
1329+  private visibleBodyLines = 1;
1330+  private bodyLines: string[] = [];
1331+  private readonly message: string;
1332 
1333   constructor(
1334     private readonly tui: TUI,
1335@@ -27,64 +29,64 @@ export class PolicyApprovalDialog {
1336     message: string,
1337     private readonly done: (approved: boolean) => void,
1338   ) {
1339-    this.message = escapeTerminalControls(message)
1340+    this.message = escapeTerminalControls(message);
1341   }
1342 
1343   handleInput(data: string): void {
1344     if (this.keybindings.matches(data, "tui.select.cancel")) {
1345-      this.done(false)
1346-      return
1347+      this.done(false);
1348+      return;
1349     }
1350     if (this.keybindings.matches(data, "tui.select.confirm")) {
1351-      this.done(this.selectedApproval)
1352-      return
1353+      this.done(this.selectedApproval);
1354+      return;
1355     }
1356     if (this.keybindings.matches(data, "tui.input.tab") || matchesKey(data, Key.left) || matchesKey(data, Key.right)) {
1357-      this.selectedApproval = !this.selectedApproval
1358-      this.tui.requestRender()
1359-      return
1360+      this.selectedApproval = !this.selectedApproval;
1361+      this.tui.requestRender();
1362+      return;
1363     }
1364     if (this.keybindings.matches(data, "tui.select.up")) {
1365-      this.scroll(-1)
1366-      return
1367+      this.scroll(-1);
1368+      return;
1369     }
1370     if (this.keybindings.matches(data, "tui.select.down")) {
1371-      this.scroll(1)
1372-      return
1373+      this.scroll(1);
1374+      return;
1375     }
1376     if (this.keybindings.matches(data, "tui.select.pageUp")) {
1377-      this.scroll(-this.visibleBodyLines)
1378-      return
1379+      this.scroll(-this.visibleBodyLines);
1380+      return;
1381     }
1382     if (this.keybindings.matches(data, "tui.select.pageDown")) {
1383-      this.scroll(this.visibleBodyLines)
1384-      return
1385+      this.scroll(this.visibleBodyLines);
1386+      return;
1387     }
1388     if (matchesKey(data, Key.home)) {
1389-      this.scrollTo(0)
1390-      return
1391+      this.scrollTo(0);
1392+      return;
1393     }
1394diff --git a/src/pi/session-override.ts b/src/pi/session-override.ts
1395index e009a7b2b629ba7117562594e6248bfbb0a4ed8a..262627a2bdec1771d9459e450424b8e88c87e67b 100644
1396--- a/src/pi/session-override.ts
1397+++ b/src/pi/session-override.ts
1398@@ -1,50 +1,50 @@
1399-export const SESSION_BASH_ALLOW_ENTRY = "policy-engine-session-bash-allow"
1400+export const SESSION_BASH_ALLOW_ENTRY = "policy-engine-session-bash-allow";
1401 
1402 export type SessionBashAllowOverride = {
1403-  source: string
1404-  pattern: RegExp
1405-}
1406+  source: string;
1407+  pattern: RegExp;
1408+};
1409 
1410 type SessionEntry = {
1411-  type?: unknown
1412-  customType?: unknown
1413-  data?: unknown
1414-}
1415+  type?: unknown;
1416+  customType?: unknown;
1417+  data?: unknown;
1418+};
1419 
1420 function sessionPattern(data: unknown, sessionId: string): { matches: boolean; source?: string } {
1421-  if (!data || typeof data !== "object" || Array.isArray(data)) return { matches: false }
1422-  const entry = data as { pattern?: unknown; sessionId?: unknown }
1423-  if (entry.sessionId !== sessionId) return { matches: false }
1424-  return { matches: true, source: typeof entry.pattern === "string" ? entry.pattern : undefined }
1425+  if (!data || typeof data !== "object" || Array.isArray(data)) return { matches: false };
1426+  const entry = data as { pattern?: unknown; sessionId?: unknown };
1427+  if (entry.sessionId !== sessionId) return { matches: false };
1428+  return { matches: true, source: typeof entry.pattern === "string" ? entry.pattern : undefined };
1429 }
1430 
1431 export function createSessionBashAllowOverride(source: string): SessionBashAllowOverride {
1432-  return { source, pattern: new RegExp(source) }
1433+  return { source, pattern: new RegExp(source) };
1434 }
1435 
1436 export function matchesSessionBashAllowOverride(
1437   override: SessionBashAllowOverride | undefined,
1438   command: string,
1439 ): boolean {
1440-  return override?.pattern.test(command) ?? false
1441+  return override?.pattern.test(command) ?? false;
1442 }
1443 
1444 export function restoreSessionBashAllowOverride(
1445   entries: readonly unknown[],
1446   sessionId: string,
1447 ): SessionBashAllowOverride | undefined {
1448-  let source: string | undefined
1449+  let source: string | undefined;
1450   for (const entry of entries) {
1451-    const { type, customType, data } = entry as SessionEntry
1452-    if (type !== "custom" || customType !== SESSION_BASH_ALLOW_ENTRY) continue
1453-    const storedPattern = sessionPattern(data, sessionId)
1454-    if (storedPattern.matches) source = storedPattern.source
1455+    const { type, customType, data } = entry as SessionEntry;
1456+    if (type !== "custom" || customType !== SESSION_BASH_ALLOW_ENTRY) continue;
1457+    const storedPattern = sessionPattern(data, sessionId);
1458+    if (storedPattern.matches) source = storedPattern.source;
1459   }
1460 
1461-  if (source === undefined) return undefined
1462+  if (source === undefined) return undefined;
1463   try {
1464-    return createSessionBashAllowOverride(source)
1465+    return createSessionBashAllowOverride(source);
1466   } catch {
1467-    return undefined
1468+    return undefined;
1469   }
1470 }
1471diff --git a/src/pi/static-permissions.ts b/src/pi/static-permissions.ts
1472index d40960bc80e1a09603967b22871ffa9131a92490..6c60b91cc0a31dc83ab39cd641b89c371390dcff 100644
1473--- a/src/pi/static-permissions.ts
1474+++ b/src/pi/static-permissions.ts
1475@@ -1,50 +1,57 @@
1476-import { realpath } from "node:fs/promises"
1477-import { homedir } from "node:os"
1478-import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"
1479-import type { ExternalDirectories, PermissionAction, PiPolicyEngineConfig } from "./config"
1480-import type { Decision, DecisionCategory } from "../core/types"
1481+import { realpath } from "node:fs/promises";
1482+import { homedir } from "node:os";
1483+import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
1484+import type { ExternalDirectories, PermissionAction, PiPolicyEngineConfig } from "./config";
1485+import type { Decision, DecisionCategory } from "../core/types";
1486 
1487-const PATH_TOOLS = new Set(["read", "write", "edit", "find", "grep", "ls"])
1488+const PATH_TOOLS = new Set(["read", "write", "edit", "find", "grep", "ls"]);
1489 
1490-type ToolInput = Record<string, unknown>
1491+type ToolInput = Record<string, unknown>;
1492 
1493 function expandedPattern(pattern: string): string {
1494-  if (pattern === "~") return homedir()
1495-  if (pattern.startsWith("~/")) return join(homedir(), pattern.slice(2))
1496-  if (pattern === "$HOME") return homedir()
1497-  if (pattern.startsWith("$HOME/")) return join(homedir(), pattern.slice(6))
1498-  return pattern
1499+  if (pattern === "~") return homedir();
1500+  if (pattern.startsWith("~/")) return join(homedir(), pattern.slice(2));
1501+  if (pattern === "$HOME") return homedir();
1502+  if (pattern.startsWith("$HOME/")) return join(homedir(), pattern.slice(6));
1503+  return pattern;
1504 }
1505 
1506 export function matchesExternalDirectory(directories: ExternalDirectories | undefined, value: string): boolean {
1507-  return directories?.some((directory) => value.startsWith(expandedPattern(directory))) ?? false
1508+  return directories?.some((directory) => value.startsWith(expandedPattern(directory))) ?? false;
1509 }
1510 
1511 async function canonicalPath(path: string, cwd: string): Promise<string> {
1512-  const absolutePath = resolve(cwd, path.startsWith("@") ? path.slice(1) : path)
1513-  const suffix: string[] = []
1514-  let existingPath = absolutePath
1515+  const absolutePath = resolve(cwd, path.startsWith("@") ? path.slice(1) : path);
1516+  const suffix: string[] = [];
1517+  let existingPath = absolutePath;
1518 
1519   while (true) {
1520     try {
1521-      return join(await realpath(existingPath), ...suffix)
1522+      return join(await realpath(existingPath), ...suffix);
1523     } catch {
1524-      const parentPath = dirname(existingPath)
1525-      if (parentPath === existingPath) return absolutePath
1526-      suffix.unshift(existingPath.slice(parentPath.length + 1))
1527-      existingPath = parentPath
1528+      const parentPath = dirname(existingPath);
1529+      if (parentPath === existingPath) return absolutePath;
1530+      suffix.unshift(existingPath.slice(parentPath.length + 1));
1531+      existingPath = parentPath;
1532     }
1533   }
1534 }
1535 
1536 function isInside(path: string, directory: string): boolean {
1537-  const pathFromDirectory = relative(directory, path)
1538-  return pathFromDirectory === "" || (!pathFromDirectory.startsWith(`..${sep}`) && pathFromDirectory !== ".." && !isAbsolute(pathFromDirectory))
1539+  const pathFromDirectory = relative(directory, path);
1540+  return (
1541+    pathFromDirectory === "" ||
1542+    (!pathFromDirectory.startsWith(`..${sep}`) && pathFromDirectory !== ".." && !isAbsolute(pathFromDirectory))
1543+  );
1544 }
1545 
1546-function staticDecision(action: PermissionAction | undefined, reason: string, category: DecisionCategory): Decision | undefined {
1547-  if (!action || action === "check") return undefined
1548-  return { decision: action, reason, category }
1549+function staticDecision(
1550+  action: PermissionAction | undefined,
1551+  reason: string,
1552+  category: DecisionCategory,
1553+): Decision | undefined {
1554+  if (!action || action === "check") return undefined;
1555+  return { decision: action, reason, category };
1556 }
1557 
1558 export async function checkStaticPermission(
1559@@ -53,22 +60,22 @@ export async function checkStaticPermission(
1560   cwd: string,
1561   config: PiPolicyEngineConfig,
1562 ): Promise<Decision | undefined> {
1563-  const action = config.tools[toolType] ?? "check"
1564+  const action = config.tools[toolType] ?? "check";
1565 
1566-  let externalPath: string | undefined
1567+  let externalPath: string | undefined;
1568   if (PATH_TOOLS.has(toolType)) {
1569-    const path = typeof input.path === "string" ? input.path : "."
1570-    const absolutePath = await canonicalPath(path, cwd)
1571-    const workspacePath = await canonicalPath(cwd, cwd)
1572+    const path = typeof input.path === "string" ? input.path : ".";
1573+    const absolutePath = await canonicalPath(path, cwd);
1574+    const workspacePath = await canonicalPath(cwd, cwd);
1575diff --git a/test/core/deterministic.test.ts b/test/core/deterministic.test.ts
1576index ccf3df0f89aefe16c22c1c28548f4f1989b93a96..f0af442c05c9dea36c10a31759898a6f859e61a8 100644
1577--- a/test/core/deterministic.test.ts
1578+++ b/test/core/deterministic.test.ts
1579@@ -1,5 +1,5 @@
1580-import { expect, test } from "bun:test"
1581-import { checkDeterministic } from "../../src/core/deterministic"
1582+import { expect, test } from "bun:test";
1583+import { checkDeterministic } from "../../src/core/deterministic";
1584 
1585 test("allows sh syntax checks without restricting the source path", () => {
1586   for (const command of [
1587@@ -9,13 +9,13 @@ test("allows sh syntax checks without restricting the source path", () => {
1588     "bash -n ../script.sh",
1589     "bash -n",
1590   ]) {
1591-    expect(checkDeterministic("bash", { command })).toMatchObject({ decision: "allow" })
1592+    expect(checkDeterministic("bash", { command })).toMatchObject({ decision: "allow" });
1593   }
1594-})
1595+});
1596 
1597 test("keeps shell execution subject to confirmation", () => {
1598-  expect(checkDeterministic("bash", { command: "bash script.sh -n" })).toMatchObject({ decision: "ask" })
1599-})
1600+  expect(checkDeterministic("bash", { command: "bash script.sh -n" })).toMatchObject({ decision: "ask" });
1601+});
1602 
1603 test("allows recognized read-only command grammars", () => {
1604   for (const command of [
1605@@ -53,9 +53,9 @@ test("allows recognized read-only command grammars", () => {
1606     "tr -d '\\n'",
1607     "cut -d: -f1,3",
1608   ]) {
1609-    expect(checkDeterministic("bash", { command })).toMatchObject({ decision: "allow" })
1610+    expect(checkDeterministic("bash", { command })).toMatchObject({ decision: "allow" });
1611   }
1612-})
1613+});
1614 
1615 test("allows local Docker operations except volume deletion", () => {
1616   for (const command of [
1617@@ -65,7 +65,7 @@ test("allows local Docker operations except volume deletion", () => {
1618     "docker run --rm app",
1619     "docker volume ls",
1620   ]) {
1621-    expect(checkDeterministic("bash", { command })).toMatchObject({ decision: "allow" })
1622+    expect(checkDeterministic("bash", { command })).toMatchObject({ decision: "allow" });
1623   }
1624 
1625   for (const command of [
1626@@ -75,12 +75,12 @@ test("allows local Docker operations except volume deletion", () => {
1627     "docker compose down -v",
1628     "docker system prune --volumes",
1629   ]) {
1630-    expect(checkDeterministic("bash", { command })).toMatchObject({ decision: "ask" })
1631+    expect(checkDeterministic("bash", { command })).toMatchObject({ decision: "ask" });
1632   }
1633-})
1634+});
1635 
1636 test("asks before allowing reads from recognized secret paths", () => {
1637-  const home = process.env.HOME ?? "/home/user"
1638+  const home = process.env.HOME ?? "/home/user";
1639   for (const command of [
1640     "cat .env",
1641     "head -n 10 config/.env.production",
1642@@ -97,17 +97,17 @@ test("asks before allowing reads from recognized secret paths", () => {
1643     "node .env",
1644     "npm --prefix .env test",
1645   ]) {
1646-    expect(checkDeterministic("bash", { command })).toMatchObject({ decision: "ask" })
1647+    expect(checkDeterministic("bash", { command })).toMatchObject({ decision: "ask" });
1648   }
1649-})
1650+});
1651 
1652 test("does not treat search terms as secret paths", () => {
1653-  expect(checkDeterministic("bash", { command: "rg '.env' README.md" })).toMatchObject({ decision: "allow" })
1654-})
1655+  expect(checkDeterministic("bash", { command: "rg '.env' README.md" })).toMatchObject({ decision: "allow" });
1656+});
1657 
1658 test("asks before stopping the Herdr server", () => {
1659-  expect(checkDeterministic("bash", { command: "herdr server stop" })).toMatchObject({ decision: "ask" })
1660-})
1661+  expect(checkDeterministic("bash", { command: "herdr server stop" })).toMatchObject({ decision: "ask" });
1662+});
1663 
1664 test("does not deterministically allow non-toolchain mutation or shell-control variants", () => {
1665   for (const command of [
1666@@ -119,6 +119,6 @@ test("does not deterministically allow non-toolchain mutation or shell-control v
1667     "kubectl kustomize deploy",
1668     "command -v node; rm -rf /",
1669   ]) {
1670-    expect(checkDeterministic("bash", { command })?.decision).not.toBe("allow")
1671+    expect(checkDeterministic("bash", { command })?.decision).not.toBe("allow");
1672   }
1673-})
1674+});
1675diff --git a/test/core/llm.test.ts b/test/core/llm.test.ts
1676index 8954df667c1399f2822ecd8f69ad528650f0ac65..3978490ec896dbed3b93b1e247b03ae17bcb2924 100644
1677--- a/test/core/llm.test.ts
1678+++ b/test/core/llm.test.ts
1679@@ -1,47 +1,58 @@
1680-import { describe, expect, test } from "bun:test"
1681-import type { UserMessage } from "@earendil-works/pi-ai"
1682-import { ModelRuntime } from "@earendil-works/pi-coding-agent"
1683-import { createPolicyReviewer } from "../../src/core/review"
1684-import type { Decision, LLMEvaluationResult, PiReasoningEffort, PolicyReviewer } from "../../src/core/types"
1685+import { describe, expect, test } from "bun:test";
1686+import type { UserMessage } from "@earendil-works/pi-ai";
1687+import { ModelRuntime } from "@earendil-works/pi-coding-agent";
1688+import { createPolicyReviewer } from "../../src/core/review";
1689+import type { Decision, LLMEvaluationResult, PiReasoningEffort, PolicyReviewer } from "../../src/core/types";
1690 
1691-const backend = process.env.POLICY_EVAL_BACKEND?.trim() ?? "llama.cpp"
1692-const baseUrl = process.env.POLICY_EVAL_BASE_URL?.trim()
1693-const provider = process.env.POLICY_EVAL_PROVIDER?.trim() ?? "openai-codex"
1694-const model = process.env.POLICY_EVAL_MODEL?.trim()
1695-const reasoningEffort = process.env.POLICY_EVAL_REASONING_EFFORT?.trim() ?? "minimal"
1696-const promptCacheKey = process.env.POLICY_EVAL_PROMPT_CACHE_KEY?.trim() ?? "policy-engine-evaluation-v1"
1697+const backend = process.env.POLICY_EVAL_BACKEND?.trim() ?? "llama.cpp";
1698+const baseUrl = process.env.POLICY_EVAL_BASE_URL?.trim();
1699+const provider = process.env.POLICY_EVAL_PROVIDER?.trim() ?? "openai-codex";
1700+const model = process.env.POLICY_EVAL_MODEL?.trim();
1701+const reasoningEffort = process.env.POLICY_EVAL_REASONING_EFFORT?.trim() ?? "minimal";
1702+const promptCacheKey = process.env.POLICY_EVAL_PROMPT_CACHE_KEY?.trim() ?? "policy-engine-evaluation-v1";
1703 
1704 function piReasoningEffort(value: string): PiReasoningEffort {
1705-  if (value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max") {
1706-    return value
1707+  if (
1708+    value === "none" ||
1709+    value === "minimal" ||
1710+    value === "low" ||
1711+    value === "medium" ||
1712+    value === "high" ||
1713+    value === "xhigh" ||
1714+    value === "max"
1715+  ) {
1716+    return value;
1717   }
1718-  throw new Error("POLICY_EVAL_REASONING_EFFORT must be a Pi reasoning level")
1719+  throw new Error("POLICY_EVAL_REASONING_EFFORT must be a Pi reasoning level");
1720 }
1721 
1722 async function createReviewer(): Promise<PolicyReviewer | undefined> {
1723   if (backend === "llama.cpp") {
1724     if (Boolean(baseUrl) !== Boolean(model)) {
1725-      throw new Error("POLICY_EVAL_BASE_URL and POLICY_EVAL_MODEL must both be set for llama.cpp evaluation")
1726+      throw new Error("POLICY_EVAL_BASE_URL and POLICY_EVAL_MODEL must both be set for llama.cpp evaluation");
1727     }
1728-    if (!baseUrl || !model) return undefined
1729-    return createPolicyReviewer({
1730-      kind: "llama.cpp",
1731-      baseUrl,
1732-      model,
1733-      enableThinking: true,
1734-    }, async () => {
1735-      throw new Error("Pi reviewer must not be used for llama.cpp evaluation")
1736-    })
1737+    if (!baseUrl || !model) return undefined;
1738+    return createPolicyReviewer(
1739+      {
1740+        kind: "llama.cpp",
1741+        baseUrl,
1742+        model,
1743+        enableThinking: true,
1744+      },
1745+      async () => {
1746+        throw new Error("Pi reviewer must not be used for llama.cpp evaluation");
1747+      },
1748+    );
1749   }
1750 
1751-  if (backend !== "pi") throw new Error('POLICY_EVAL_BACKEND must be "llama.cpp" or "pi"')
1752-  if (!model) throw new Error("POLICY_EVAL_MODEL must be set for Pi evaluation")
1753+  if (backend !== "pi") throw new Error('POLICY_EVAL_BACKEND must be "llama.cpp" or "pi"');
1754+  if (!model) throw new Error("POLICY_EVAL_MODEL must be set for Pi evaluation");
1755 
1756-  const runtime = await ModelRuntime.create()
1757-  const piModel = runtime.getModel(provider, model)
1758-  if (!piModel) throw new Error(`Pi model is unavailable: ${provider}/${model}`)
1759-  const effort = piReasoningEffort(reasoningEffort)
1760-  const usesOpenAIPromptCache = piModel.api === "openai-codex-responses" || piModel.api === "openai-responses"
1761+  const runtime = await ModelRuntime.create();
1762+  const piModel = runtime.getModel(provider, model);
1763+  if (!piModel) throw new Error(`Pi model is unavailable: ${provider}/${model}`);
1764+  const effort = piReasoningEffort(reasoningEffort);
1765+  const usesOpenAIPromptCache = piModel.api === "openai-codex-responses" || piModel.api === "openai-responses";
1766 
1767   return createPolicyReviewer(
1768     {
1769@@ -52,14 +63,14 @@ async function createReviewer(): Promise<PolicyReviewer | undefined> {
1770       ...(usesOpenAIPromptCache && { promptCacheKey }),
1771     },
1772     async (_config, messages, _maxTokens, context) => {
1773-      const systemPrompt = messages.find((message) => message.role === "system")?.content
1774+      const systemPrompt = messages.find((message) => message.role === "system")?.content;
1775       const userMessages: UserMessage[] = messages
1776         .filter((message) => message.role === "user")
1777         .map((message) => ({
1778           role: "user",
1779diff --git a/test/setup.ts b/test/setup.ts
1780index 4aba665b25fd5e3e6802463c5927935225a50df2..ff5f241d138fcd6e189bf358fb9b0cffac74b830 100644
1781--- a/test/setup.ts
1782+++ b/test/setup.ts
1783@@ -1,12 +1,12 @@
1784-import { mkdtempSync, rmSync } from "node:fs"
1785-import { tmpdir } from "node:os"
1786-import { join } from "node:path"
1787+import { mkdtempSync, rmSync } from "node:fs";
1788+import { tmpdir } from "node:os";
1789+import { join } from "node:path";
1790 
1791 if (process.env.POLICY_EVAL_BACKEND?.trim() !== "pi") {
1792-  const stateDirectory = mkdtempSync(join(tmpdir(), "policy-engine-state-"))
1793-  process.env.PI_CODING_AGENT_DIR = stateDirectory
1794+  const stateDirectory = mkdtempSync(join(tmpdir(), "policy-engine-state-"));
1795+  process.env.PI_CODING_AGENT_DIR = stateDirectory;
1796 
1797   process.on("exit", () => {
1798-    rmSync(stateDirectory, { recursive: true, force: true })
1799-  })
1800+    rmSync(stateDirectory, { recursive: true, force: true });
1801+  });
1802 }