pi-warden 0.7.0 → 0.9.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/dist/shape.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { isMode } from "./config.js";
2
+ import { DEFAULT_TEMPLATES } from "./widget.js";
2
3
  /** The config layout this extension build expects; compared with the loaded config module's CONFIG_SCHEMA. */
3
- export const EXPECTED_SCHEMA = 3;
4
+ export const EXPECTED_SCHEMA = 5;
4
5
  const off = { enabled: false };
5
6
  const proseOff = () => ({ ...off, audience: "technical", threshold: 1, trend: 3, minChars: 1 });
6
7
  /**
@@ -30,8 +31,11 @@ export function completeConfig(loaded) {
30
31
  done: section("done", { ...off, claimsDone: 1, nudge: false }),
31
32
  slop: section("slop", { ...off, threshold: 1, prose: proseOff() }),
32
33
  security: section("security", { ...off, threshold: 1 }),
34
+ rules: section("rules", { ...off, threshold: 1, files: [], fallback: false, maxChars: 500, exclude: [], skip: [], sensitivePaths: {} }),
33
35
  context: section("context", { ...off, tailMinChars: 1, confidence: 1, duplicateMinChars: Number.MAX_SAFE_INTEGER, recallTool: "none", formatConfidence: 1 }),
34
- widget: section("widget", { ...off, placement: "aboveEditor", shortcut: "", panelWidth: "40%", action: "", stuck: "", done: "", prose: "", security: "", context: "" }),
36
+ runaway: section("runaway", { ...off, repeats: Number.MAX_SAFE_INTEGER, thinkingRepeats: Number.MAX_SAFE_INTEGER, minChars: Number.MAX_SAFE_INTEGER, recover: false }),
37
+ notify: section("notify", { ...off, cooldownMs: 0, command: [] }),
38
+ widget: section("widget", { ...off, placement: "aboveEditor", shortcut: "", panelWidth: "40%", action: "", stuck: "", done: "", prose: "", security: "", context: "", runaway: "", rules: "" }),
35
39
  };
36
40
  if (typeof config.slop.prose !== "object" || config.slop.prose === null) {
37
41
  missing.push("slop.prose");
@@ -44,6 +48,11 @@ export function completeConfig(loaded) {
44
48
  }
45
49
  if (typeof config.widget.panelWidth !== "string" && typeof config.widget.panelWidth !== "number")
46
50
  config.widget = { ...config.widget, panelWidth: "40%" };
51
+ // The runaway guard added its template later than the other sections; an older widget section renders the default line.
52
+ if (typeof config.widget.runaway !== "string")
53
+ config.widget = { ...config.widget, runaway: DEFAULT_TEMPLATES.runaway };
54
+ if (typeof config.widget.rules !== "string")
55
+ config.widget = { ...config.widget, rules: DEFAULT_TEMPLATES.rules };
47
56
  return { config, missing };
48
57
  }
49
58
  export function shapeWarning(missing, loadedSchema) {
package/dist/trace.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import type { DoneVerdict } from "./done.js";
2
2
  import type { Verdict } from "./guard.js";
3
3
  import type { ProseVerdict } from "./prose.js";
4
+ import type { RulesVerdict } from "./rules.js";
5
+ import type { RunawayVerdict } from "./runaway.js";
4
6
  import type { Attempt, StuckVerdict } from "./stuck.js";
5
- export type GuardName = "action" | "stuck" | "done" | "prose" | "security" | "context";
7
+ export type GuardName = "action" | "stuck" | "done" | "prose" | "security" | "context" | "runaway" | "rules";
6
8
  export interface TraceEntry {
7
9
  at: number;
8
10
  guard: GuardName;
@@ -28,4 +30,6 @@ export declare function actionDetails(verdict: Verdict, extra?: {
28
30
  }): string[];
29
31
  export declare function stuckDetails(verdict: StuckVerdict, attempts: readonly Attempt[], told?: string): string[];
30
32
  export declare function doneDetails(verdict: DoneVerdict, finalMessage: string, told?: string): string[];
33
+ export declare function runawayDetails(verdict: RunawayVerdict, told: string | undefined, recovering: boolean): string[];
31
34
  export declare function proseDetails(verdict: ProseVerdict, reply: string, audience: string, told?: string): string[];
35
+ export declare function rulesDetails(verdict: RulesVerdict, told?: string): string[];
package/dist/trace.js CHANGED
@@ -81,6 +81,16 @@ export function doneDetails(verdict, finalMessage, told) {
81
81
  lines.push(`agent told: ${clip(told, 400)}`);
82
82
  return lines;
83
83
  }
84
+ export function runawayDetails(verdict, told, recovering) {
85
+ const lines = [
86
+ `repeated: ${clip(verdict.block, 200)}`,
87
+ `${verdict.count} times in ${verdict.chars} chars of ${verdict.kind}; signal: ${verdict.signal === "block" ? "identical paragraph" : "recurring trailing phrase"}`,
88
+ recovering ? "run stopped; one follow-up turn started" : "run stopped; not restarted",
89
+ ];
90
+ if (told)
91
+ lines.push(`agent told: ${clip(told, 400)}`);
92
+ return lines;
93
+ }
84
94
  export function proseDetails(verdict, reply, audience, told) {
85
95
  const lines = [`reply: ${clip(reply.replace(/\s+/g, " "), 300)}`, `audience: ${audience}`];
86
96
  if (verdict.scores)
@@ -93,3 +103,17 @@ export function proseDetails(verdict, reply, audience, told) {
93
103
  lines.push(`agent told: ${clip(told, 400)}`);
94
104
  return lines;
95
105
  }
106
+ export function rulesDetails(verdict, told) {
107
+ const lines = [`${verdict.tool} ${verdict.path}; rules from ${verdict.sources.join(", ") || "nowhere"}${verdict.aggregate ? " (judged as one document)" : ""}; ${verdict.asked} question${verdict.asked === 1 ? "" : "s"}`];
108
+ if (verdict.scores?.length)
109
+ lines.push(`jev: ${verdict.scores.map(score => `${score.name} ${score.outcome.replace(/_/g, " ")} ${percent(score.violation)}`).join(" · ")}${verdict.model ? ` · ${verdict.model} · ${verdict.elapsedMs} ms` : ""}`);
110
+ if (verdict.findings.length)
111
+ lines.push(`violations: ${verdict.findings.map(finding => `${finding.name} (${percent(finding.violation)})`).join("; ")}${verdict.editId ? ` in ${verdict.editId.replace("_", " ")}` : ""}`);
112
+ if (verdict.skippedReason)
113
+ lines.push(`skipped: ${verdict.skippedReason}`);
114
+ if (verdict.error)
115
+ lines.push(`typesafe: ${verdict.error}`);
116
+ if (told)
117
+ lines.push(`agent told: ${clip(told, 400)}`);
118
+ return lines;
119
+ }
package/dist/widget.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { DoneVerdict } from "./done.js";
2
2
  import type { Verdict } from "./guard.js";
3
3
  import type { ProseVerdict } from "./prose.js";
4
+ import type { RulesVerdict } from "./rules.js";
5
+ import type { RunawayVerdict } from "./runaway.js";
4
6
  import type { StuckVerdict } from "./stuck.js";
5
7
  export type WidgetPlacement = "aboveEditor" | "belowEditor";
6
8
  export interface WidgetConfig {
@@ -17,6 +19,8 @@ export interface WidgetConfig {
17
19
  prose: string;
18
20
  security: string;
19
21
  context: string;
22
+ runaway: string;
23
+ rules: string;
20
24
  }
21
25
  export declare const DEFAULT_TEMPLATES: {
22
26
  readonly action: "warden · {tool} · irreversible {irreversible} · off-task {offTask} · {scope} · slop: {slop} · patterns: {patterns} · {flags} · {level}";
@@ -25,6 +29,8 @@ export declare const DEFAULT_TEMPLATES: {
25
29
  readonly prose: "warden · prose · wordy {wordy} · clichés {cliches} · jargon {jargon} · {flags} · {status}";
26
30
  readonly security: "warden · security · {tool} · injection {injection} · exfiltration {exfiltration} · {status}";
27
31
  readonly context: "warden · context · {tool} · {retention} · saved {bytesSaved} bytes";
32
+ readonly runaway: "warden · runaway · {kind} · {count}× repeated · {chars} chars · {signal} · {status}";
33
+ readonly rules: "warden · rules · {tool} {path} · {asked} rules · {violations} · {status}";
28
34
  };
29
35
  export declare function defaultWidgetConfig(): WidgetConfig;
30
36
  export type Tokens = Record<string, string | undefined>;
@@ -35,12 +41,16 @@ export type Tokens = Record<string, string | undefined>;
35
41
  export declare function renderTemplate(template: string, tokens: Tokens): string;
36
42
  export declare function actionTokens(verdict: Verdict, at?: number): Tokens;
37
43
  export declare function stuckTokens(verdict: StuckVerdict, at?: number): Tokens;
44
+ export declare function runawayTokens(verdict: RunawayVerdict, recovering: boolean, at?: number): Tokens;
38
45
  export declare function doneTokens(verdict: DoneVerdict, at?: number): Tokens;
39
46
  export declare function proseTokens(verdict: ProseVerdict, at?: number): Tokens;
47
+ export declare function rulesTokens(verdict: RulesVerdict, at?: number): Tokens;
40
48
  /** Token names users can put in templates, for /warden status and the README. */
41
49
  export declare const TOKEN_NAMES: {
42
50
  readonly security: readonly ["tool", "injection", "exfiltration", "status"];
43
51
  readonly context: readonly ["tool", "retention", "bytesSaved"];
52
+ readonly runaway: readonly ["kind", "count", "chars", "signal", "block", "status", "time", "guard"];
53
+ readonly rules: readonly ["tool", "path", "asked", "violations", "status", "source", "reasons", "model", "ms", "flags", "time", "guard"];
44
54
  readonly action: readonly ["tool", "level", "source", "irreversible", "offTask", "scope", "approved", "slop", "slopStub", "slopComments", "slopDead", "slopHedging", "patterns", "reasons", "path", "model", "ms", "flags", "time", "guard"];
45
55
  readonly prose: readonly ["wordy", "cliches", "jargon", "status", "reasons", "model", "ms", "flags", "time", "guard"];
46
56
  readonly stuck: readonly ["failures", "sameStrategy", "approachChange", "progress", "status", "source", "reasons", "model", "ms", "flags", "time", "guard"];
package/dist/widget.js CHANGED
@@ -5,6 +5,8 @@ export const DEFAULT_TEMPLATES = {
5
5
  prose: "warden · prose · wordy {wordy} · clichés {cliches} · jargon {jargon} · {flags} · {status}",
6
6
  security: "warden · security · {tool} · injection {injection} · exfiltration {exfiltration} · {status}",
7
7
  context: "warden · context · {tool} · {retention} · saved {bytesSaved} bytes",
8
+ runaway: "warden · runaway · {kind} · {count}× repeated · {chars} chars · {signal} · {status}",
9
+ rules: "warden · rules · {tool} {path} · {asked} rules · {violations} · {status}",
8
10
  };
9
11
  export function defaultWidgetConfig() {
10
12
  return { enabled: true, placement: "aboveEditor", shortcut: "ctrl+shift+w", panelWidth: "40%", ...DEFAULT_TEMPLATES };
@@ -81,6 +83,18 @@ export function stuckTokens(verdict, at = Date.now()) {
81
83
  flags: flags || undefined,
82
84
  };
83
85
  }
86
+ export function runawayTokens(verdict, recovering, at = Date.now()) {
87
+ return {
88
+ guard: "runaway",
89
+ time: time(at),
90
+ kind: verdict.kind,
91
+ count: String(verdict.count),
92
+ chars: String(verdict.chars),
93
+ signal: verdict.signal,
94
+ block: verdict.block,
95
+ status: recovering ? "stopped, recovering" : "stopped",
96
+ };
97
+ }
84
98
  export function doneTokens(verdict, at = Date.now()) {
85
99
  return {
86
100
  guard: "done",
@@ -113,10 +127,28 @@ export function proseTokens(verdict, at = Date.now()) {
113
127
  flags: verdict.error ? "typesafe error" : undefined,
114
128
  };
115
129
  }
130
+ export function rulesTokens(verdict, at = Date.now()) {
131
+ return {
132
+ guard: "rules",
133
+ time: time(at),
134
+ tool: verdict.tool,
135
+ path: verdict.path,
136
+ asked: verdict.source === "skipped" ? undefined : String(verdict.asked),
137
+ violations: verdict.findings.length ? verdict.findings.map(finding => `${finding.name} ${finding.violation.toFixed(2)}`).join(", ") : verdict.scores ? "none" : undefined,
138
+ status: verdict.source === "error" ? "typesafe error" : verdict.source === "skipped" ? "skipped" : verdict.findings.length ? "violation" : "ok",
139
+ source: verdict.source,
140
+ reasons: verdict.skippedReason ?? (verdict.findings.length ? verdict.findings.map(finding => finding.name).join("; ") : undefined),
141
+ model: verdict.model,
142
+ ms: verdict.elapsedMs === undefined ? undefined : String(verdict.elapsedMs),
143
+ flags: verdict.error ? "typesafe error" : undefined,
144
+ };
145
+ }
116
146
  /** Token names users can put in templates, for /warden status and the README. */
117
147
  export const TOKEN_NAMES = {
118
148
  security: ["tool", "injection", "exfiltration", "status"],
119
149
  context: ["tool", "retention", "bytesSaved"],
150
+ runaway: ["kind", "count", "chars", "signal", "block", "status", "time", "guard"],
151
+ rules: ["tool", "path", "asked", "violations", "status", "source", "reasons", "model", "ms", "flags", "time", "guard"],
120
152
  action: ["tool", "level", "source", "irreversible", "offTask", "scope", "approved", "slop", "slopStub", "slopComments", "slopDead", "slopHedging", "patterns", "reasons", "path", "model", "ms", "flags", "time", "guard"],
121
153
  prose: ["wordy", "cliches", "jargon", "status", "reasons", "model", "ms", "flags", "time", "guard"],
122
154
  stuck: ["failures", "sameStrategy", "approachChange", "progress", "status", "source", "reasons", "model", "ms", "flags", "time", "guard"],
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-warden",
3
- "version": "0.7.0",
4
- "description": "Guardrails for Pi built on pi-typesafe that steer the agent instead of interrupting you: Jev judges irreversible and off-task tool calls before they run, detects stuck loops, checks unverified done claims, and flags slop.",
3
+ "version": "0.9.0",
4
+ "description": "Guardrails for Pi built on pi-typesafe that steer the agent instead of interrupting you: Jev judges irreversible and off-task tool calls before they run, checks written code against your project rules, detects stuck loops, checks unverified done claims, and flags slop.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Ryan Gapac",