pi-warden 0.9.0 → 0.9.1

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/README.md CHANGED
@@ -6,6 +6,8 @@ A second pair of eyes for [Pi](https://pi.dev). It watches what the agent does,
6
6
 
7
7
  The verdicts above are real output from `npm run test:live`. Independent project; not affiliated with TypeSafe AI or the Pi authors. Built on [pi-typesafe](https://github.com/DevMortimer/pi-typesafe).
8
8
 
9
+ Default configs and a starter rules file are in [`examples/`](examples/).
10
+
9
11
  ## What it does
10
12
 
11
13
  | Guard | Watches | Does |
@@ -42,6 +44,8 @@ Requires Pi 0.85 or newer and Node.js 22.19 or newer. Pattern checks work with n
42
44
 
43
45
  `TYPESAFE_API_KEY` in the environment takes precedence over the stored key. Headless runs give consent with `PI_WARDEN_ENABLED=1`.
44
46
 
47
+ The [examples README](examples/README.md) walks through the starter `pi-warden.md`, the project `.pi/pi-warden.json`, and the user `config.json`.
48
+
45
49
  ## Philosophy
46
50
 
47
51
  pi-warden is a harness for the agent, not a gate for you. Three jobs:
@@ -72,7 +76,7 @@ The action guard receives your latest message plus up to eight earlier user and
72
76
 
73
77
  ### Rules
74
78
 
75
- Write your project's rules as Markdown headings in `pi-warden.md` at the project root:
79
+ Write your project's rules as Markdown headings in `pi-warden.md` at the project root (a starter file with a dozen rules is in [`examples/pi-warden.md`](examples/pi-warden.md)):
76
80
 
77
81
  ```markdown
78
82
  # No console statements
@@ -226,7 +230,7 @@ User file `~/.pi/agent/pi-warden/config.json` (owner-only). Missing keys use the
226
230
 
227
231
  A project may add `.pi/pi-warden.json` with `enabled` and per-guard overrides: stricter thresholds, extra guarded tools, `rules.files`, `rules.skip`, `rules.sensitivePaths`, or `"done": { "enabled": false }`. Project files are read only when Pi trusts the project. They can never grant `typesafe` consent, change `mode`, raise `timeoutMs` or `maxRequests`, or set `notify.command`. Environment: `PI_WARDEN_ENABLED=1` (consent), `PI_WARDEN_MODE=steer|confirm|advise`.
228
232
 
229
- A wince-style setup for a backend repo:
233
+ A wince-style setup for a backend repo (the full version is [`examples/pi-warden.json`](examples/pi-warden.json)):
230
234
 
231
235
  ```json
232
236
  {
@@ -256,7 +260,7 @@ Obvious credentials (`Authorization` headers, `TOKEN=` and `SECRET=` assignments
256
260
 
257
261
  ## After updating the package
258
262
 
259
- Run `/reload` in each open Pi session after an update. If a session reports `Cannot read properties of undefined (reading 'enabled')` while a fresh session works, restart it: the loaded modules do not match the files on disk. Since 0.5.2 the extension checks the shape of the config it receives; a missing section (an extension module and a config module from different versions in one process) switches that guard off and prints one warning naming the sections and the schema numbers. If you see it, restart Pi.
263
+ Restart Pi after an update; `/reload` re-imports the entry module but can leave older modules of the same package in memory. Since 0.5.2 the extension checks the shape of the config it receives; a section that an older module does not know (the symptom of two package versions in one process) switches that guard off and prints one warning naming the sections and the schema numbers. 0.9.0 crashed instead when the shape-check module itself was the stale one; since 0.9.1 the extension guards the sections it reads in its own module, so the warning appears and everything else keeps working. If you see the warning, restart Pi.
260
264
 
261
265
  ## For extension authors
262
266
 
@@ -1,6 +1,7 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { ToolCallRef } from "./action-guard.js";
3
3
  import type { SlopSymptom, Verdict } from "./guard.js";
4
+ import type { ShapeResult } from "./shape.js";
4
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; 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; and redacted tool-output samples for security and context saving (retention and output format). Compression and duplicate notes store an exact, owner-only copy in a temporary file on this machine. 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
6
  /**
6
7
  * Tool calls of the assistant message being preflighted. Pi runs `tool_call` hooks for sibling calls one after another,
@@ -10,5 +11,11 @@ export declare function siblingToolCalls(ctx: ExtensionContext): ToolCallRef[];
10
11
  export declare function confirmMessage(verdict: Verdict): string;
11
12
  /** Names each symptom and its fix; repeats in the session turn the note into a standing rule. */
12
13
  export declare function slopSteer(where: string, symptoms: readonly SlopSymptom[], counts: Record<SlopSymptom, number>): string;
14
+ /**
15
+ * `completeConfig` in shape.ts fills sections an older config module lacks, but shape.ts can be the stale module too: the
16
+ * 0.8 version knew nothing of `rules`, so a 0.9 extension read `config.rules.enabled` on undefined and every tool call
17
+ * failed until Pi was restarted. The sections this build reads are therefore checked here as well, in the module that reads them.
18
+ */
19
+ export declare function guardCurrentSections(result: ShapeResult): ShapeResult;
13
20
  /** Native Pi registration; importing the root library does not load this module. */
14
21
  export default function wardenExtension(pi: ExtensionAPI): void;
package/dist/extension.js CHANGED
@@ -18,7 +18,7 @@ import { openTracePanel } from "./panel.js";
18
18
  import { completeConfig, shapeWarning } from "./shape.js";
19
19
  import { ContextLedger, formatLedger } from "./saver.js";
20
20
  import { actionDetails, doneDetails, proseDetails, rulesDetails, runawayDetails, stuckDetails, Trace } from "./trace.js";
21
- import { proseTokens, renderTemplate, TOKEN_NAMES } from "./widget.js";
21
+ import { DEFAULT_TEMPLATES, proseTokens, renderTemplate, TOKEN_NAMES } from "./widget.js";
22
22
  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; 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; and redacted tool-output samples for security and context saving (retention and output format). Compression and duplicate notes store an exact, owner-only copy in a temporary file on this machine. Requests may incur charges. Secret redaction is best-effort. Results are model judgments, not proof or authorization; offline pattern checks stay active either way.";
23
23
  const WIDGET = PACKAGE_NAME;
24
24
  const CONFIRM_TEXT_LIMIT = 500;
@@ -115,6 +115,28 @@ export function slopSteer(where, symptoms, counts) {
115
115
  const standing = symptoms.some(symptom => counts[symptom] >= 3) ? " Treat this as a standing rule for the rest of the session." : "";
116
116
  return `pi-warden: the content just written to ${where} has ${named}. Fix it in your next edit: ${fixes}.${standing}`;
117
117
  }
118
+ /**
119
+ * `completeConfig` in shape.ts fills sections an older config module lacks, but shape.ts can be the stale module too: the
120
+ * 0.8 version knew nothing of `rules`, so a 0.9 extension read `config.rules.enabled` on undefined and every tool call
121
+ * failed until Pi was restarted. The sections this build reads are therefore checked here as well, in the module that reads them.
122
+ */
123
+ export function guardCurrentSections(result) {
124
+ const { config } = result;
125
+ const missing = [...result.missing];
126
+ if (typeof config.rules !== "object" || config.rules === null || !Array.isArray(config.rules.exclude)) {
127
+ if (!missing.includes("rules"))
128
+ missing.push("rules");
129
+ config.rules = { enabled: false, threshold: 1, files: [], fallback: false, maxChars: 500, exclude: [], skip: [], sensitivePaths: {} };
130
+ }
131
+ if (typeof config.widget !== "object" || config.widget === null) {
132
+ if (!missing.includes("widget"))
133
+ missing.push("widget");
134
+ config.widget = { ...defaultConfig().widget, enabled: false };
135
+ }
136
+ if (typeof config.widget.rules !== "string")
137
+ config.widget = { ...config.widget, rules: DEFAULT_TEMPLATES.rules };
138
+ return { config, missing };
139
+ }
118
140
  /** Native Pi registration; importing the root library does not load this module. */
119
141
  export default function wardenExtension(pi) {
120
142
  let client;
@@ -144,7 +166,7 @@ export default function wardenExtension(pi) {
144
166
  // A partially updated module graph can hand this build a config without the sections it expects; see shape.ts.
145
167
  let shapeReported = false;
146
168
  const configFor = (ctx) => {
147
- const { config, missing } = completeConfig(loadConfig({ cwd: ctx.cwd, projectTrusted: ctx.isProjectTrusted() }));
169
+ const { config, missing } = guardCurrentSections(completeConfig(loadConfig({ cwd: ctx.cwd, projectTrusted: ctx.isProjectTrusted() })));
148
170
  if (missing.length && !shapeReported) {
149
171
  shapeReported = true;
150
172
  // A namespace read stays undefined (not a link error) when an older config module lacks the export.
package/dist/guard.js CHANGED
@@ -64,7 +64,8 @@ const GIT_MESSAGE_SUBCOMMANDS = new Set(["commit", "tag", "notes", "merge", "sta
64
64
  /** Interpreters whose stdin script can still run shell commands; their heredoc bodies stay in scope when they do. */
65
65
  const INTERPRETERS = /^(?:python[\d.]*|node|ruby|perl|php|deno|bun|tsx|Rscript|lua[\d.]*)$/;
66
66
  const EXEC_CALLS = /\b(?:os\.system|os\.popen|os\.exec\w*|subprocess|child_process|execSync|spawnSync|execFileSync|spawn\(|exec\(|system\(|popen\(|shell_exec|passthru|proc_open|Open3|IO\.popen|Deno\.run|Deno\.Command|Bun\.spawn|Bun\.\$|%x[\[{(]|`[^`\n]*\b(?:rm|git|dd|mkfs|kubectl|terraform)\b)/;
67
- const HEREDOC = /<<-?\s*(?:"(\w+)"|'(\w+)'|\\?(\w+))/;
67
+ const HEREDOC = /<<-?\s*(?:"(\w+)"|'(\w+)'|(\\)?(\w+))/;
68
+ const SUBSTITUTION = /\$\(|`/;
68
69
  function headOf(segment) {
69
70
  const tokens = segment.trim().split(/\s+/);
70
71
  let index = 0;
@@ -73,7 +74,10 @@ function headOf(segment) {
73
74
  const head = tokens[index];
74
75
  return head ? head.replace(/^.*\//, "") : undefined;
75
76
  }
76
- /** Quoted strings replaced by a placeholder; escapes inside double quotes are honoured, single quotes take everything. */
77
+ /**
78
+ * Quoted strings replaced by a placeholder; escapes inside double quotes are honoured, single quotes take everything.
79
+ * A double-quoted string that substitutes a command (`"$(...)"`, backticks) executes it, so that string stays visible.
80
+ */
77
81
  function blankQuotes(segment) {
78
82
  let out = "";
79
83
  for (let index = 0; index < segment.length; index++) {
@@ -89,7 +93,8 @@ function blankQuotes(segment) {
89
93
  out += segment.slice(index);
90
94
  break;
91
95
  }
92
- out += `${char}[text]${char}`;
96
+ const inner = segment.slice(index + 1, end);
97
+ out += char === "\"" && SUBSTITUTION.test(inner) ? `${char}${inner}${char}` : `${char}[text]${char}`;
93
98
  index = end;
94
99
  }
95
100
  return out;
@@ -111,8 +116,6 @@ function isDataSegment(segment) {
111
116
  * (`python3 - <<EOF`) are kept when the script calls out to a shell or process API.
112
117
  */
113
118
  export function stripDataText(command) {
114
- if (/\$\(|`/.test(command))
115
- return { text: command, stripped: false };
116
119
  const lines = command.split("\n");
117
120
  const out = [];
118
121
  let stripped = false;
@@ -123,14 +126,18 @@ export function stripDataText(command) {
123
126
  out.push(line);
124
127
  continue;
125
128
  }
126
- const delimiter = heredoc[1] ?? heredoc[2] ?? heredoc[3];
127
- const consumer = headOf(line.slice(0, heredoc.index)) ?? "";
129
+ const delimiter = heredoc[1] ?? heredoc[2] ?? heredoc[4];
130
+ // 'EOF', "EOF", and \EOF make the body literal; a bare EOF body is expanded, so a substitution inside it runs.
131
+ const literal = heredoc[4] === undefined || heredoc[3] !== undefined;
128
132
  const body = [];
129
133
  let close = index + 1;
130
134
  while (close < lines.length && lines[close].replace(/^\t+/, "") !== delimiter)
131
135
  body.push(lines[close]), close++;
132
136
  const bodyText = body.join("\n");
133
- const executed = SHELL_SINKS.has(consumer) || (INTERPRETERS.test(consumer) && EXEC_CALLS.test(bodyText));
137
+ // The whole pipeline on the heredoc line counts: `cat <<EOF | bash` executes the body as much as `bash <<EOF` does.
138
+ const heads = splitShell(line).map(segment => headOf(segment) ?? "");
139
+ const consumer = headOf(line.slice(0, heredoc.index)) ?? "";
140
+ const executed = heads.some(head => SHELL_SINKS.has(head)) || (INTERPRETERS.test(consumer) && EXEC_CALLS.test(bodyText)) || (!literal && SUBSTITUTION.test(bodyText));
134
141
  out.push(line);
135
142
  if (executed)
136
143
  out.push(...body);
@@ -144,10 +151,11 @@ export function stripDataText(command) {
144
151
  }
145
152
  const joined = out.join("\n");
146
153
  const segments = splitShell(joined);
154
+ // A shell sink anywhere may run text written earlier in the same command (`cat <<EOF > run.sh` then `bash run.sh`), so nothing is treated as data.
147
155
  if (segments.some(segment => { const head = headOf(segment); return head !== undefined && SHELL_SINKS.has(head); }))
148
- return { text: joined, stripped };
156
+ return { text: command, stripped: false };
149
157
  if (/\b(?:ba|z|da|k)?sh\s+-[a-zA-Z]*c\b/.test(joined))
150
- return { text: joined, stripped };
158
+ return { text: command, stripped: false };
151
159
  let text = joined;
152
160
  for (const segment of segments) {
153
161
  if (!isDataSegment(segment) || !/["']/.test(segment))
@@ -162,13 +170,13 @@ export function stripDataText(command) {
162
170
  }
163
171
  /**
164
172
  * rm with both recursive and force flags. Absolute, home, variable, or wildcard targets are destructive; relative ones are
165
- * risky. A quote before `rm` is allowed so `bash -c "rm -rf /"` is read; data quotes were blanked before this runs.
173
+ * risky. A quote or parenthesis before `rm` is allowed so a quoted or substituted command is read; data quotes were blanked before this runs.
166
174
  */
167
175
  function classifyRm(segment, cwd) {
168
- const match = /(?:^|[\s"'])rm\s+(.*)$/.exec(segment);
176
+ const match = /(?:^|[\s"'(])rm\s+(.*)$/.exec(segment);
169
177
  if (!match)
170
178
  return undefined;
171
- const tokens = match[1].split(/\s+/).filter(Boolean).map(token => token.replace(/["']$/, ""));
179
+ const tokens = match[1].split(/\s+/).filter(Boolean).map(token => token.replace(/["')]+$/, ""));
172
180
  const flags = tokens.filter(token => token.startsWith("-"));
173
181
  const targets = tokens.filter(token => !token.startsWith("-"));
174
182
  const recursive = flags.some(flag => flag === "--recursive" || (/^-[a-zA-Z]+$/.test(flag) && /[rR]/.test(flag)));
@@ -0,0 +1,45 @@
1
+ # Get started with pi-warden
2
+
3
+ Three files, each optional. Copy what you need and edit it.
4
+
5
+ | File in this folder | Copy it to | What it does |
6
+ | --- | --- | --- |
7
+ | `pi-warden.md` | `<your project>/pi-warden.md` | Your project's rules. Each `#` heading is one rule; Jev judges every write and edit against them and tells the agent which rule it broke. |
8
+ | `pi-warden.json` | `<your project>/.pi/pi-warden.json` | Project settings: which files the rules skip, which are never sent to Jev, sensitive paths that give the agent a note, tuned thresholds. Read only when Pi trusts the project. |
9
+ | `config.json` | `~/.pi/agent/pi-warden/config.json` | Your personal settings: consent, mode, whether steers show in the transcript, the audience for reply checks, notifications. `/warden config` opens it in Pi's editor. |
10
+
11
+ ## Step by step
12
+
13
+ 1. Install and enable:
14
+
15
+ ```bash
16
+ pi install npm:pi-warden
17
+ ```
18
+
19
+ Then in Pi run `/warden enable`, read the notice, and paste a key from [console.typesafe.ai](https://console.typesafe.ai) if you have none stored. Without a key pi-warden still runs its offline pattern checks; the rules guard needs Jev.
20
+
21
+ 2. Copy `pi-warden.md` to your project root. Delete the rules you do not want, write your own. Run `/warden status`; the `Rules:` line should say `pi-warden.md (N rules)`.
22
+
23
+ 3. Ask the agent to write something that breaks a rule, for example "add a console.log to src/index.ts". The write goes through, the status line shows `warden · rules · write src/index.ts · N rules · No console output in library code 0.9x · violation`, and the agent gets the rule text as a steer. Open the trace (`ctrl+shift+w`) to read exactly what it was told.
24
+
25
+ 4. Optional: copy `pi-warden.json` to `.pi/pi-warden.json` for path scoping and sensitive-path notes, and `config.json` to `~/.pi/agent/pi-warden/config.json` for your own defaults.
26
+
27
+ ## Writing rules that work
28
+
29
+ - One heading, one rule. Jev answers one question per rule, so a heading that bundles three things gets one blurry answer.
30
+ - Say what a violation looks like. "A bare `TODO` without a ticket" beats "TODOs should be tidy".
31
+ - Scope with `paths:` when a rule is language-specific or does not apply to tests. Globs: `**` any depth, `*` within one segment, a pattern without a slash matches at any depth.
32
+ - Code fences inside a rule are fine; a `#` inside a fence is not read as a heading.
33
+ - Rules cost tokens: one request per write or edit, roughly 600 tokens plus about 150 per rule plus the content sample. At most 31 rules are asked per request.
34
+ - No `pi-warden.md`? pi-warden falls back to `README.md`, `CLAUDE.md`, or `AGENTS.md` and asks one question about the whole document (cut to `rules.maxChars`, headings kept). It works, but a dedicated rules file gives sharper answers and names the rule. Set `"rules": { "fallback": false }` to turn the fallback off.
35
+
36
+ ## Checking the rules from the command line
37
+
38
+ ```bash
39
+ node -e '
40
+ import("pi-warden").then(({ RuleStore, defaultConfig }) => {
41
+ const set = new RuleStore().load(process.cwd(), defaultConfig().rules);
42
+ for (const rule of set?.rules ?? []) console.log(rule.id, rule.paths.length ? rule.paths.join(",") : "(all files)");
43
+ if (set?.dropped) console.log(`${set.dropped} rules beyond the cap are ignored`);
44
+ });'
45
+ ```
@@ -0,0 +1,26 @@
1
+ {
2
+ "typesafe": true,
3
+ "mode": "steer",
4
+ "steerVisible": false,
5
+ "rules": {
6
+ "fallback": true,
7
+ "maxChars": 8000
8
+ },
9
+ "slop": {
10
+ "prose": { "audience": "a founder without a programming background" }
11
+ },
12
+ "context": {
13
+ "confidence": 0.8,
14
+ "recallTool": "auto"
15
+ },
16
+ "notify": {
17
+ "enabled": true,
18
+ "cooldownMs": 10000,
19
+ "command": []
20
+ },
21
+ "widget": {
22
+ "placement": "aboveEditor",
23
+ "shortcut": "ctrl+shift+w",
24
+ "panelWidth": "40%"
25
+ }
26
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "rules": {
3
+ "threshold": 0.7,
4
+ "skip": ["tests/**", "test/**", "**/*.test.*", "**/*.spec.*", "docs/**", "**/*.md"],
5
+ "exclude": ["secrets/**", "**/*.pem", "**/*.key", "fixtures/**"],
6
+ "sensitivePaths": {
7
+ "**/migrations/**": "This touches a database migration: tell the user in your reply and make sure there is a rollback path",
8
+ "**/permissions*": "Access control changed: tell the user and ask for a security review before this is merged",
9
+ "**/auth/**": "Authentication code changed: tell the user; do not weaken any check without saying so",
10
+ ".github/workflows/**": "CI configuration changed: tell the user what the pipeline will now do differently"
11
+ }
12
+ },
13
+ "action": {
14
+ "tools": ["bash", "powershell", "ctx_execute", "ctx_batch_execute", "ctx_execute_file", "write", "edit"],
15
+ "irreversible": { "warn": 0.5, "confirm": 0.7 },
16
+ "offTask": { "warn": 0.6, "confirm": 0.85 }
17
+ },
18
+ "slop": {
19
+ "prose": { "audience": "technical" }
20
+ },
21
+ "done": { "enabled": true }
22
+ }
@@ -0,0 +1,53 @@
1
+ Copy this file to the root of your project as `pi-warden.md` and edit it. Every `#` heading below is one rule. The text
2
+ under a heading is what Jev reads when it judges a write or an edit. A `paths:` line right under a heading limits that rule
3
+ to matching files. Text above the first heading, like this paragraph, is ignored, so keep the intro heading-free.
4
+
5
+ Keep rules short and concrete. Say what a violation looks like. Jev is good at "must not contain X" and "every X needs a Y";
6
+ it is worse at taste ("code should be clean").
7
+
8
+ # No console output in library code
9
+ paths: src/**
10
+ Code under `src/` must not call `console.log`, `console.debug`, or `console.info`. Use the project logger or remove the
11
+ call before committing. Tests and scripts may print.
12
+
13
+ # TODO comments need a reference
14
+ A `TODO` or `FIXME` comment must name a ticket or issue, for example `TODO(APP-123): ...`. A bare `TODO` is a violation.
15
+
16
+ # No explicit any
17
+ paths: **/*.ts, **/*.tsx
18
+ Do not use the `any` type. Use a precise type, `unknown` with a runtime check, or a generic parameter.
19
+
20
+ # Exported functions declare their return type
21
+ paths: src/**/*.ts
22
+ Every exported function or method declares its return type instead of relying on inference.
23
+
24
+ # Errors are not swallowed
25
+ A `catch` block must handle the error, log it with context, or rethrow it. An empty `catch`, or one that only has a comment
26
+ such as `// ignore`, is a violation.
27
+
28
+ # Switch statements have a default case
29
+ Every `switch` has a `default` branch, even if it only throws on an unexpected value.
30
+
31
+ # No hardcoded secrets
32
+ Source code must not contain passwords, API keys, or tokens. They come from configuration or a secrets manager. A test fixture
33
+ with an obviously fake value such as `test-key` is fine.
34
+
35
+ # Boolean names read as a question
36
+ A boolean variable or property starts with `is`, `has`, `should`, `can`, or a similar predicate prefix, for example
37
+ `isEnabled`, not `enabled`.
38
+
39
+ # New exported functions get a test
40
+ paths: src/**
41
+ A newly added exported function or class comes with at least one test that exercises its main behaviour. Changing an
42
+ existing function does not require a new test by itself.
43
+
44
+ # Comments explain why, not what
45
+ A comment states a reason, a constraint, a workaround, or a non-obvious invariant. A comment that restates what the next line
46
+ plainly does is a violation.
47
+
48
+ # No commented-out code
49
+ Delete code that is no longer used. Do not leave it behind as comments.
50
+
51
+ # Migrations are reversible
52
+ paths: **/migrations/**
53
+ Every migration has a down step (or an explicit note in the file that the change cannot be reversed and why).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-warden",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
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",
@@ -37,6 +37,7 @@
37
37
  "files": [
38
38
  "dist",
39
39
  "extensions",
40
+ "examples",
40
41
  "README.md",
41
42
  "LICENSE"
42
43
  ],