pi-warden 0.35.0 → 0.36.0

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/CHANGELOG.md CHANGED
@@ -6,6 +6,16 @@ How to keep this current: add the entry in the same pull request as the change,
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ <!-- Empty. Next release starts here. -->
10
+
11
+ ## 0.36.0
12
+
13
+ ### Changed
14
+ - The resolved rules file content no longer rides the action request when the rules guard is off. `rules.enabled: false` now means no rules file content leaves the machine at all; with the guard on, the request is unchanged. `EvaluateOptions.rules` and `InspectOptions.rules` carry the switch, and a library caller that omits it keeps the earlier behaviour. The disclosure and `docs/data-handling.md` say so.
15
+
16
+ ### Docs
17
+ - `docs/configuration.md`: `Recipe: security work` — what each guard sends off the machine and what `/warden disable` leaves behind, a local-only user profile, a lab/CTF project profile with the exemptions security work needs, and an out-of-scope deny rule. README privacy paragraph points to it.
18
+
9
19
  ## 0.35.0
10
20
 
11
21
  ### Added
package/README.md CHANGED
@@ -82,7 +82,7 @@ These are project-maintained benchmarks, not universal claims. Raw reports and r
82
82
 
83
83
  ## Privacy
84
84
 
85
- Depending on the configured judgment backend, Warden sends to your provider: a redacted summary of each guarded call, the agent's plan, up to eight redacted prior messages, and redacted tool-output samples. Secrets are stripped before anything leaves the machine. [Full details.](docs/data-handling.md)
85
+ Depending on the configured judgment backend, Warden sends to your provider: a redacted summary of each guarded call, the agent's plan, up to eight redacted prior messages, and redacted tool-output samples. Secrets are stripped before anything leaves the machine. [Full details.](docs/data-handling.md) Security work — pentesting, incident response, CTF — has its own configuration recipe for keeping samples local and for the friction that credentials and lab setups cause: [Recipe: security work](docs/configuration.md#recipe-security-work).
86
86
 
87
87
  ## Documentation
88
88
 
@@ -1,4 +1,4 @@
1
- import type { ActionGuardConfig, SecurityConfig, SlopGuardConfig } from "./config.js";
1
+ import type { ActionGuardConfig, RulesConfig, SecurityConfig, SlopGuardConfig } from "./config.js";
2
2
  import type { PreviousAction, TaskMessage, Verdict } from "./guard.js";
3
3
  import type { Judge } from "pi-typesafe";
4
4
  /** One tool call as the agent proposed it. `id` is Pi's tool call id, stable across hooks and retries. */
@@ -25,6 +25,8 @@ export interface InspectOptions {
25
25
  signal?: AbortSignal | undefined;
26
26
  slop?: SlopGuardConfig | undefined;
27
27
  security?: SecurityConfig | undefined;
28
+ /** The rules guard's switch; off keeps the rules file content out of the action request. Omitted, it is sent as before. */
29
+ rules?: Pick<RulesConfig, "enabled"> | undefined;
28
30
  /** Calls allowed in the previous turn; the regret question about them rides this call's request, never a sibling's. */
29
31
  previousActions?: readonly PreviousAction[] | undefined;
30
32
  }
@@ -20,7 +20,7 @@ export class ActionGuard {
20
20
  const { task } = conversation;
21
21
  // A hold happened under an earlier prompt and the user has since replied: ask whether the reply approves this action.
22
22
  const retryAfterHold = this.holdPending && this.lastHoldPrompt !== task;
23
- const judgeCall = (tool, input, previousActions) => evaluateAction({ tool, input, cwd: options.cwd, task, context: conversation.context, plan: conversation.plan }, { config: options.config, judge: options.judge, signal: options.signal, slop: options.slop, security: options.security, retryAfterHold, previousActions });
23
+ const judgeCall = (tool, input, previousActions) => evaluateAction({ tool, input, cwd: options.cwd, task, context: conversation.context, plan: conversation.plan }, { config: options.config, judge: options.judge, signal: options.signal, slop: options.slop, security: options.security, rules: options.rules, retryAfterHold, previousActions });
24
24
  // A retry after a hold stays sequential because an approval consumed by one sibling changes the question for the next.
25
25
  if (options.judge && !retryAfterHold) {
26
26
  for (const sibling of conversation.siblings ?? []) {
@@ -2,7 +2,7 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
2
2
  import type { ToolCallRef } from "./action-guard.js";
3
3
  import type { SlopSymptom, Verdict } from "./guard.js";
4
4
  import type { ShapeResult } from "./shape.js";
5
- export declare const disclosure = "With TypeSafe judgments enabled, pi-warden sends to api.typesafe.ai: your latest request and up to eight redacted prior user/assistant text messages for task context, plus a redacted, truncated summary of each guarded bash, write, or edit call before it runs, with the agent's own words from the message that makes the call (its stated plan); the resolved active rules file content (pi-warden.md, the configured files, or README/CLAUDE/AGENTS as fallback, token-aware truncated at ~4000 tokens) sent with every action request, including calls where the rules guard is disabled; for a write or edit in a project with a rules file (pi-warden.md, the configured files, or README/CLAUDE/AGENTS as fallback), a larger redacted sample of the written content with the current file around each edit and the rule text; the last few tool calls and output tails when the agent keeps failing; the agent's final message when it reports completion without running checks; redacted tool-output samples for security and context saving (retention and output format); a redacted sample of an async subagent report that names a failure, a stop, or a question, with your latest request, when warden decides whether that report should wake the agent; and, on the first guarded call after your reply, the redacted summaries of the calls allowed in the previous turn, so Jev can say whether your reply regrets one of them. Compression and duplicate notes store an exact, owner-only copy in a temporary file on this machine; the hold feedback log stores tool names, pattern ids, scores, and outcomes (never commands) in an owner-only file under Pi's agent directory; an owner-only SQLite database under Pi's agent directory stores redacted hold context (plan, summary, redacted command preview, outcomes) for held and judged-allowed calls, for learning and retention (configurable, default 365 days). Requests may incur charges. Secret redaction is best-effort. Results are model judgments, not proof or authorization; offline pattern checks stay active either way.";
5
+ export declare const disclosure = "With TypeSafe judgments enabled, pi-warden sends to api.typesafe.ai: your latest request and up to eight redacted prior user/assistant text messages for task context, plus a redacted, truncated summary of each guarded bash, write, or edit call before it runs, with the agent's own words from the message that makes the call (its stated plan); the resolved active rules file content (pi-warden.md, the configured files, or README/CLAUDE/AGENTS as fallback, token-aware truncated at ~4000 tokens) sent with every action request unless the rules guard is off (`rules.enabled: false`), which keeps that content on this machine; for a write or edit in a project with a rules file (pi-warden.md, the configured files, or README/CLAUDE/AGENTS as fallback), a larger redacted sample of the written content with the current file around each edit and the rule text; the last few tool calls and output tails when the agent keeps failing; the agent's final message when it reports completion without running checks; redacted tool-output samples for security and context saving (retention and output format); a redacted sample of an async subagent report that names a failure, a stop, or a question, with your latest request, when warden decides whether that report should wake the agent; and, on the first guarded call after your reply, the redacted summaries of the calls allowed in the previous turn, so Jev can say whether your reply regrets one of them. Compression and duplicate notes store an exact, owner-only copy in a temporary file on this machine; the hold feedback log stores tool names, pattern ids, scores, and outcomes (never commands) in an owner-only file under Pi's agent directory; an owner-only SQLite database under Pi's agent directory stores redacted hold context (plan, summary, redacted command preview, outcomes) for held and judged-allowed calls, for learning and retention (configurable, default 365 days). Requests may incur charges. Secret redaction is best-effort. Results are model judgments, not proof or authorization; offline pattern checks stay active either way.";
6
6
  /** Which guard spent the user's attention. The status line reports one count per guard. */
7
7
  export type SteerGuard = "action" | "rules" | "security" | "stuck" | "done" | "prose" | "runaway" | "subagent";
8
8
  /**
package/dist/extension.js CHANGED
@@ -32,7 +32,7 @@ import { ContextLedger, formatLedger } from "./saver.js";
32
32
  import { formatWake, newReports, reportLabel, triageReport, WakePolicy } from "./subagent.js";
33
33
  import { actionDetails, doneDetails, proseDetails, rulesDetails, runawayDetails, stuckDetails, Trace } from "./trace.js";
34
34
  import { actionTokens, DEFAULT_TEMPLATES, LEVEL_COLOR, pickSentenceTemplate, proseTokens, renderTemplate, SENTENCE_TEMPLATES, statusWidget, TOKEN_NAMES } from "./widget.js";
35
- export const disclosure = "With TypeSafe judgments enabled, pi-warden sends to api.typesafe.ai: your latest request and up to eight redacted prior user/assistant text messages for task context, plus a redacted, truncated summary of each guarded bash, write, or edit call before it runs, with the agent's own words from the message that makes the call (its stated plan); the resolved active rules file content (pi-warden.md, the configured files, or README/CLAUDE/AGENTS as fallback, token-aware truncated at ~4000 tokens) sent with every action request, including calls where the rules guard is disabled; for a write or edit in a project with a rules file (pi-warden.md, the configured files, or README/CLAUDE/AGENTS as fallback), a larger redacted sample of the written content with the current file around each edit and the rule text; the last few tool calls and output tails when the agent keeps failing; the agent's final message when it reports completion without running checks; redacted tool-output samples for security and context saving (retention and output format); a redacted sample of an async subagent report that names a failure, a stop, or a question, with your latest request, when warden decides whether that report should wake the agent; and, on the first guarded call after your reply, the redacted summaries of the calls allowed in the previous turn, so Jev can say whether your reply regrets one of them. Compression and duplicate notes store an exact, owner-only copy in a temporary file on this machine; the hold feedback log stores tool names, pattern ids, scores, and outcomes (never commands) in an owner-only file under Pi's agent directory; an owner-only SQLite database under Pi's agent directory stores redacted hold context (plan, summary, redacted command preview, outcomes) for held and judged-allowed calls, for learning and retention (configurable, default 365 days). Requests may incur charges. Secret redaction is best-effort. Results are model judgments, not proof or authorization; offline pattern checks stay active either way.";
35
+ export const disclosure = "With TypeSafe judgments enabled, pi-warden sends to api.typesafe.ai: your latest request and up to eight redacted prior user/assistant text messages for task context, plus a redacted, truncated summary of each guarded bash, write, or edit call before it runs, with the agent's own words from the message that makes the call (its stated plan); the resolved active rules file content (pi-warden.md, the configured files, or README/CLAUDE/AGENTS as fallback, token-aware truncated at ~4000 tokens) sent with every action request unless the rules guard is off (`rules.enabled: false`), which keeps that content on this machine; for a write or edit in a project with a rules file (pi-warden.md, the configured files, or README/CLAUDE/AGENTS as fallback), a larger redacted sample of the written content with the current file around each edit and the rule text; the last few tool calls and output tails when the agent keeps failing; the agent's final message when it reports completion without running checks; redacted tool-output samples for security and context saving (retention and output format); a redacted sample of an async subagent report that names a failure, a stop, or a question, with your latest request, when warden decides whether that report should wake the agent; and, on the first guarded call after your reply, the redacted summaries of the calls allowed in the previous turn, so Jev can say whether your reply regrets one of them. Compression and duplicate notes store an exact, owner-only copy in a temporary file on this machine; the hold feedback log stores tool names, pattern ids, scores, and outcomes (never commands) in an owner-only file under Pi's agent directory; an owner-only SQLite database under Pi's agent directory stores redacted hold context (plan, summary, redacted command preview, outcomes) for held and judged-allowed calls, for learning and retention (configurable, default 365 days). Requests may incur charges. Secret redaction is best-effort. Results are model judgments, not proof or authorization; offline pattern checks stay active either way.";
36
36
  const WIDGET = PACKAGE_NAME;
37
37
  const CONFIRM_TEXT_LIMIT = 500;
38
38
  const freshStats = () => ({ inspected: 0, judged: 0, warned: 0, held: 0, approved: 0, offPlan: 0, offTask: 0, slop: 0, ruleChecks: 0, ruleViolations: 0, pathNotes: 0, stuckChecks: 0, stuck: 0, doneChecks: 0, unverified: 0, proseChecks: 0, proseNudges: 0, runaway: 0, errors: 0, steers: 0, steersSkipped: 0, steerGuards: {}, subagentReports: 0, subagentWoken: 0, restatements: 0 });
@@ -701,7 +701,7 @@ export default function wardenExtension(pi) {
701
701
  ? rulesGuard.inspect(call, siblings, { cwd: ctx.cwd, config: config.rules, judge, timeoutMs: config.timeoutMs, signal: ctx.signal })
702
702
  : undefined;
703
703
  rulesCheck?.catch(() => undefined);
704
- const verdict = await actionGuard.inspect(call, { task, context: recentTaskContext(ctx), siblings, plan: assistantPlan(ctx) }, { config: config.action, cwd: ctx.cwd, judge, signal: ctx.signal, slop: config.slop, security: config.security, previousActions: regretCandidates.length ? regretCandidates : undefined });
704
+ const verdict = await actionGuard.inspect(call, { task, context: recentTaskContext(ctx), siblings, plan: assistantPlan(ctx) }, { config: config.action, cwd: ctx.cwd, judge, signal: ctx.signal, slop: config.slop, security: config.security, rules: config.rules, previousActions: regretCandidates.length ? regretCandidates : undefined });
705
705
  if (verdict.source === "skipped")
706
706
  return;
707
707
  // Arming check: if any armed rule's command regex matches this call, inject a hit into the verdict.
@@ -1558,7 +1558,7 @@ export default function wardenExtension(pi) {
1558
1558
  const judge = judgeFor(config);
1559
1559
  if (judge && ctx.hasUI && !await ctx.ui.confirm("Send one synthetic pi-warden test request?", `A synthetic action ("rm -rf /tmp/pi-warden-demo" for the task "Prepare the demo environment") goes to ${backendHost(config.typesafeBackend)} and may incur charges. ${disclosureFor(config.typesafeBackend, disclosure)}`))
1560
1560
  return;
1561
- const verdict = await evaluateAction({ tool: "bash", input: { command: "rm -rf /tmp/pi-warden-demo" }, cwd: ctx.cwd, task: "Prepare the demo environment" }, { config: { ...config.action, enabled: true, tools: ["bash"] }, judge });
1561
+ const verdict = await evaluateAction({ tool: "bash", input: { command: "rm -rf /tmp/pi-warden-demo" }, cwd: ctx.cwd, task: "Prepare the demo environment" }, { config: { ...config.action, enabled: true, tools: ["bash"] }, judge, rules: config.rules });
1562
1562
  const deliveryVerdict = ctx.hasUI ? verdict : agentDeliveryVerdict(verdict);
1563
1563
  const deliveryReasons = deliveryVerdict.reasons;
1564
1564
  const fmt = formatVerdictTokens(verdict, config.widget.action);
package/dist/guard.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { IntegrationErrorCode, Judge, Questions } from "pi-typesafe";
2
- import type { ActionGuardConfig, ArmingRule, CommandRule, PathRule, SecurityConfig, SlopGuardConfig } from "./config.js";
2
+ import type { ActionGuardConfig, ArmingRule, CommandRule, PathRule, RulesConfig, SecurityConfig, SlopGuardConfig } from "./config.js";
3
3
  export type Level = "allow" | "warn" | "confirm" | "deny";
4
4
  export type Severity = "destructive" | "risky" | "sensitive" | "deny";
5
5
  export type ViolationSource = "pattern" | "rules-guard" | "security-guard" | "slop-guard";
@@ -170,6 +170,12 @@ export interface EvaluateOptions {
170
170
  /** Adds quality questions for write/edit content to the same request. */
171
171
  slop?: SlopGuardConfig | undefined;
172
172
  security?: SecurityConfig | undefined;
173
+ /**
174
+ * The rules guard's switch. The active rules file rides every judged action request, so `enabled: false` is what keeps
175
+ * that content on this machine. Omitted, the file is sent as before, so a library caller that passes no rules config
176
+ * keeps today's behaviour.
177
+ */
178
+ rules?: Pick<RulesConfig, "enabled"> | undefined;
173
179
  /** This exact call was held earlier and the user has replied since: ask whether the reply approves it. */
174
180
  retryAfterHold?: boolean | undefined;
175
181
  /** Calls allowed in the previous turn: ask whether the user's latest message regrets one of them (rides this request). */
package/dist/guard.js CHANGED
@@ -876,8 +876,9 @@ export async function evaluateAction(action, options) {
876
876
  }
877
877
  if (!judge)
878
878
  return withPlan({ level, source: "pattern", summary, patterns, reasons });
879
- // Resolve the rules file once per call for the Jev request state.
880
- const resolved = resolveRulesFile(action.cwd);
879
+ // Resolve the rules file once per call for the Jev request state. With the rules guard off, no rules content leaves
880
+ // the machine at all: no question names the field, so an absent one costs nothing.
881
+ const resolved = options.rules?.enabled === false ? null : resolveRulesFile(action.cwd);
881
882
  const floorHits = builtInHits.length ? builtInHits.join("; ") : "none";
882
883
  const request = buildRequest(summary, action.task, { slop: options.slop?.enabled ?? false, approval: options.retryAfterHold ?? false, security: options.security?.enabled ?? false, context: action.context, previousActions: options.previousActions, plan, questions: options.questions, rules: resolved?.content, rulesSource: resolved?.source, violations: remainingViolations, floorHits });
883
884
  const result = await ask(judge, request, { timeoutMs: config.timeoutMs, ...(options.signal ? { signal: options.signal } : {}) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-warden",
3
- "version": "0.35.0",
3
+ "version": "0.36.0",
4
4
  "description": "Makes the Pi agent follow your project's rules. Jev judges every write against your pi-warden.md and quotes the broken rule back to the agent, names slop, breaks stuck loops, calls out unverified done claims, compresses large tool output, and holds the rare destructive command. Built on pi-typesafe.",
5
5
  "type": "module",
6
6
  "license": "MIT",