pi-warden 0.26.0 → 0.34.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +224 -0
  2. package/README.md +58 -174
  3. package/dist/audit.d.ts +22 -0
  4. package/dist/audit.js +110 -0
  5. package/dist/config.d.ts +28 -0
  6. package/dist/config.js +57 -0
  7. package/dist/conscience-config.d.ts +21 -0
  8. package/dist/conscience-config.js +82 -0
  9. package/dist/conscience-loader.d.ts +26 -0
  10. package/dist/conscience-loader.js +82 -0
  11. package/dist/conscience-policy.d.ts +24 -0
  12. package/dist/conscience-policy.js +28 -0
  13. package/dist/conscience-privacy.d.ts +4 -0
  14. package/dist/conscience-privacy.js +11 -0
  15. package/dist/discover.d.ts +14 -0
  16. package/dist/discover.js +85 -0
  17. package/dist/extension.d.ts +1 -1
  18. package/dist/extension.js +346 -61
  19. package/dist/extension.js.bak +346 -61
  20. package/dist/guard.d.ts +132 -1
  21. package/dist/guard.js +452 -31
  22. package/dist/holds.d.ts +2 -0
  23. package/dist/holds.js +7 -1
  24. package/dist/index.d.ts +14 -6
  25. package/dist/index.js +8 -4
  26. package/dist/init.d.ts +36 -0
  27. package/dist/init.js +199 -0
  28. package/dist/laya-download.d.ts +40 -0
  29. package/dist/laya-download.js +288 -0
  30. package/dist/laya-judge.d.ts +62 -0
  31. package/dist/laya-judge.js +495 -0
  32. package/dist/learning.d.ts +81 -5
  33. package/dist/learning.js +268 -35
  34. package/dist/output.d.ts +31 -1
  35. package/dist/output.js +80 -1
  36. package/dist/panel.d.ts +29 -0
  37. package/dist/panel.js +257 -10
  38. package/dist/redact.js +9 -4
  39. package/dist/rules-file.d.ts +18 -0
  40. package/dist/rules-file.js +72 -0
  41. package/dist/rules.d.ts +8 -0
  42. package/dist/rules.js +54 -3
  43. package/dist/saver.d.ts +2 -0
  44. package/dist/saver.js +4 -0
  45. package/dist/shape.js +11 -2
  46. package/dist/tool-recommendation.d.ts +15 -0
  47. package/dist/tool-recommendation.js +31 -0
  48. package/dist/trace.d.ts +2 -0
  49. package/dist/widget.d.ts +98 -0
  50. package/dist/widget.js +262 -1
  51. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -8,6 +8,230 @@ How to keep this current: add the entry in the same pull request as the change,
8
8
 
9
9
  <!-- Empty. Next release starts here. -->
10
10
 
11
+ ## 0.34.1
12
+
13
+ ### Fixed
14
+ - `scripts/calibrate-action.mjs`: `--yes` now spends what the corpus needs instead of stopping at the 2000-request default; `--max-requests N` is an explicit cap that `--yes` does not lift, and a run it stops early says how many replays it skipped and writes `report-latest-partial.md`.
15
+
16
+ ### Docs
17
+ - Action guard calibration on 315 recorded sessions at 0.33.3 under `eval/reports/2026-09-21-calibration-0.33.3/`, with the headline table in `docs/guards.md`.
18
+ - README measurement paragraph reworded for readability, same numbers.
19
+
20
+ ## 0.34.0
21
+
22
+ ### Added
23
+ - `scripts/hold-stats.mjs`: read-only script reporting per-project and total hold statistics from the SQLite database, with `--json` output and `PI_WARDEN_DB` support.
24
+ - `/warden status` now shows a lifetime hold sentence for the current project root (e.g. "Lifetime here: 55 holds, 11 labeled, 0 stood (0/11), 114 allowed accepted, 0 regretted.").
25
+
26
+ ### Docs
27
+ - README "Does it actually help?" section now includes a measurement-on-real-use paragraph with current hold numbers, date, and the link to the floor-evidence decision in 0.33.0.
28
+
29
+ ## 0.33.4
30
+
31
+ ### Fixed
32
+ - Rules guard no longer judges prose-only fallback documents (README.md, CLAUDE.md, AGENTS.md) as one rule; a fallback with no rule-shaped sections is skipped.
33
+
34
+ ## 0.33.3
35
+
36
+ ### Tests
37
+ - Live smoke stuck suite now exercises the Jev-judged path: two new cases reach `source: "typesafe"` and print real scores; the existing offline-repeat case prints `offline repeat` instead of three `undefined` values.
38
+
39
+ ## 0.33.2
40
+
41
+ ### Fixed
42
+ - Tests and eval runs now write to an isolated database via `PI_WARDEN_DB` instead of polluting the user's `holds.db`.
43
+
44
+ ## 0.33.1
45
+
46
+ ### Tests
47
+ - Live smoke suite cap raised from 60 to 100 requests; summary line now reports budget-error misses.
48
+
49
+ ## 0.33.0
50
+
51
+ ### Changed
52
+ - Built-in pattern floor is now evidence when a judge answers (`action.floor: "evidence"`, default). Built-in hits (shell rules, rm classifier, sensitive-path, outside-project) are fed to the judge as `floor_hits` in the request state and traced as `(evidence)`, but they no longer override the judge's `irreversible` score. User-declared rules keep their declared action. `action.floor: "level"` restores the legacy behaviour. Replay on 52 real holds: evidence mode drops held count from 52 to 28; level mode preserves 44 confirm + 8 warn.
53
+
54
+ ### Fixed
55
+ - Deferred sensitive-path hit now warns (instead of silently allowing) when the judge fails in evidence mode with `failOpen: true`.
56
+
57
+ ## 0.32.0
58
+
59
+ ### Fixed
60
+ - `replanned` outcomes now persist to SQLite, closing the gap where re-plan labels were set in memory but never written to the database.
61
+ - `command_preview` stores the redacted command or path (capped at 200 chars) instead of the bare tool name.
62
+ - Outcome known at record time (dialog-approved/declined) is no longer lost to a race between `noteOutcomes` and `learningIds.set`.
63
+ - Set `PRAGMA busy_timeout` on the SQLite connection and skip VACUUM when no rows are pruned, preventing SQLITE_BUSY on startup with concurrent sessions. Fixes #36.
64
+
65
+ ### Changed
66
+ - Judged allowed calls (`held = 0`) are now stored in SQLite alongside held calls, enabling precision and false-negative rate computation.
67
+ - Disclosure and `docs/data-handling.md` updated to reflect that judged allowed calls are stored with `held = 0`.
68
+
69
+ ## 0.31.0
70
+
71
+ ### Added
72
+ - `/warden audit` command: agent-driven workspace audit that sends a prompt to the session model. The model reads source, finds concrete Jev opportunities, produces measurable evidence, and writes an HTML report to `.pi-warden/audit-report.html`.
73
+
74
+ ### Changed
75
+ - Bumped pi-typesafe dependency to ^0.6.1.
76
+
77
+ ## 0.30.4
78
+
79
+ ### Changed
80
+ - Made `should_proceed` trace-only by default until calibrated, so low scores no longer ask the agent to pause. Set `action.shouldProceed.steer: true` to restore the existing steer; the `hold` threshold is unchanged.
81
+
82
+ ## 0.30.3
83
+
84
+ ### Fixed
85
+ - Live status follows the newest verdict, including guards without sentence tokens; stack entries retain guard update order within their severity groups.
86
+ - Action warning and hold sentences use verdict reasons instead of inferring irreversibility from the level or unrelated scores.
87
+
88
+ ## 0.30.2
89
+
90
+ ### Fixed
91
+ - The live widget bar wraps its sentence to the pane width; one over-wide line tripped pi's render-width guard and aborted the session. Fixes #29.
92
+
93
+ ## 0.30.1
94
+
95
+ ### Fixed
96
+ - Approval question now accepts generic task-level approval ("proceed", "yes", "go ahead", "sure") instead of requiring explicit approval of each specific action. Fixes the pattern where the model keeps holding after the user approved the whole task.
97
+
98
+ ### Changed
99
+ - Approval question uses intent-based wording that lets Jev reason about approval directly rather than matching specific words.
100
+ - Bumped pi-typesafe dependency to ^0.6.1.
101
+
102
+ ## 0.30.0
103
+
104
+ ### Added
105
+ - Violation pipeline: deterministic per-violation authorization, escalation, and aggregation for pattern-detected and rules-guard violations.
106
+ - `Violation`, `ViolationScope`, `Authorization`, `EscalatedViolation` types for the violation pipeline.
107
+ - `authorize()`, `isNegated()`, `scopeMatches()`, `isAuthEligible()` (severity-based) for deterministic authorization.
108
+ - `escalateBlastRadius()` and `escalateRulesViolation()` for Jev-backed severity escalation.
109
+ - `aggregateLevel()` for final tool-call level from remaining violations.
110
+ - `resolveRulesFile()` and `extractRules()` for token-aware rules file resolution.
111
+ - `checkPiWardenMissing()` for first-run warning support.
112
+ - `escalationThreshold` config key in `ActionGuardConfig` (default 0.85).
113
+ - `/warden init` command: scaffolds a starter pi-warden.md with safety rules and project-type-specific rules.
114
+ - `writeStarterRules()`, `generateStarterRules()`, `detectProjectType()`, `buildProjectContext()` in `src/init.ts`.
115
+ - First-run warning when pi-warden.md is missing and a fallback is active.
116
+ - Per-violation noul questions sent to Jev on the same request, returning P(yes) as a confidence value. Answers drive escalation (`escalateBlastRadius`, `escalateRulesViolation`) and aggregation (`aggregateLevel`) in the action guard pipeline. Calibrated: AUC 0.73 against regret, 0.42 against rejected turns (600 sessions, 2026-09-20).
117
+ - `parseViolationJudgments()` for safe parsing of Jev noul responses with defaults for missing/malformed data; supports legacy choice fallback.
118
+ - Resolved rules file (`rules`, `rulesSource`) passed to Jev in the request state.
119
+ - `should_proceed` noul question on every action request: unified gate covering rule violations, unrequested scope, explicit constraint breaches, and material user decisions. Calibrated: AUC 0.26 against regret, 0.58 against rejected turns (600 sessions, 2026-09-20).
120
+ - `action.shouldProceed` config key `{ hold: number }` (default 0.6): steer threshold for `should_proceed`.
121
+ - `shouldProceedQuestion` exported from `src/guard.ts`.
122
+ - `Verdict.shouldProceedSteer` flag for the unified gate steer.
123
+ - `Judgment.shouldProceed` field for traceability.
124
+ - `matchPathRules` is now exported for use by conscience-loader.
125
+
126
+ ### Changed
127
+ - `ViolationScope` no longer has a `labels` field; scope matching uses paths only.
128
+ - `Violation` no longer has an `authEligible` field; eligibility is derived from severity in `authorize()`.
129
+ - `isAuthEligible()` now takes a `Severity` parameter instead of a `PatternHit`.
130
+ - `escalateRulesViolation()` threshold check now matches `escalateBlastRadius()` style (guard clause for no-escalation).
131
+
132
+ ### Tests
133
+ - 38 tests for authorization, escalation, aggregation, violation judgment parsing, rules-file resolution, and first-run warning.
134
+
135
+ ## 0.29.3
136
+
137
+ ### Fixed
138
+
139
+ - Trace-only off-task findings no longer reach the agent through dedicated steers, generic headless warnings, or the headless `/warden test` report. Independent warning and confirmation reasons on the same call still deliver normally.
140
+
141
+ ## 0.29.2
142
+
143
+ ### Added
144
+
145
+ - GitHub Actions CI for pull requests, main pushes, version tags, and manual runs: workflow lint plus typecheck, offline tests, and build on Node 22.19.0, 24, and 26.
146
+ - Package installation smoke checks and downloadable npm tarballs with SHA-256 checksums; validated version tags prepare draft GitHub releases without publishing to npm.
147
+
148
+ ### Docs
149
+
150
+ - Documented CI checks and manual release steps, and corrected the contributor version-bump instructions.
151
+
152
+ ## 0.29.1
153
+
154
+ ### Changed
155
+
156
+ - Extracted duplicated credential-key regex alternation in `redact.ts` into a shared `CREDENTIAL_KEYS` constant.
157
+
158
+ ## 0.29.0
159
+
160
+ ### Changed
161
+
162
+ - Off-task is now gated on the categorical scope answer instead of the score alone (AUC 0.51). `expected_step` vetoes off-task entirely; `unrelated` always warns; `plausible_side_step` warns trace-only. All off-task steers are trace-only until AUC clears 0.51.
163
+ - Ledger records now include a `callExcerpt` (tool + path) for auditing off-task and intent-mismatch warns.
164
+
165
+ ## 0.28.4
166
+
167
+ ### Fixed
168
+
169
+ - Extension no longer crashes on load when node:sqlite is unavailable (e.g. some Node v25 builds). Learning features disabled gracefully; all guards still work.
170
+
171
+ ## 0.28.3
172
+
173
+ ### Fixed
174
+
175
+ - Temp output directories from saveOutput() are now cleaned up at session start (~2.5 MB/session leak).
176
+
177
+ ## 0.28.2
178
+
179
+ ### Fixed
180
+
181
+ - One-time warning when confirm mode falls back to steer in headless sessions.
182
+
183
+ ## 0.28.1
184
+
185
+ ### Fixed
186
+
187
+ - TypeSafe consent disclosure now mentions the SQLite hold database, its contents, and the retention policy.
188
+
189
+ ## 0.28.0
190
+
191
+ ### Added
192
+
193
+ - \`learning.retentionDays\` config key (default 365) — prunes hold records older than this on startup. \`0\` disables pruning.
194
+
195
+ ### Fixed
196
+
197
+ - SQLite hold database grew unboundedly; now pruned on startup with VACUUM.
198
+ - Empty catch blocks in learning.ts now log warnings instead of silently swallowing errors.
199
+
200
+ ## 0.27.2
201
+
202
+ ### Fixed
203
+
204
+ - Redact user prompts before storing in SQLite hold records (JSONL was already safe).
205
+
206
+ ## 0.27.1
207
+
208
+ ### Removed
209
+
210
+ - Dead `SessionCompressionTracker` class — `getMultiplier()` and `record()` were never called outside tests. ~50 lines removed.
211
+
212
+ ## 0.27.0
213
+
214
+ ### Added
215
+
216
+ - Sentence-style status bar templates (70+ across all 9 guards). `pickSentenceTemplate()` selects the best template from token context; zero LLM cost.
217
+ - `widget.barMode: "live"` (default): status bar shows one sentence like *"warden allowed bash action — write to src/config.ts"* instead of data-style tokens.
218
+ - `widget.barMode: "stack"`: the previous multi-guard data-style view.
219
+ - Compact sidebar mode: trace entries default to 1–3 lines; press `d` to toggle detailed view.
220
+ - Interactive config panel (`/warden config`): arrow-key navigation, Enter to toggle booleans or edit values, `s` to save.
221
+ - `/warden config set <key.path> <value>` and `/warden config get <key.path>` for quick CLI edits.
222
+ - `TraceEntry.tokens` field for live-mode re-rendering with sentence templates.
223
+ - `formatVerdictTokens()` returns both rendered line and raw tokens.
224
+ - `setNestedValue`, `getNestedValue`, `parseConfigValue` config helpers.
225
+
226
+ ### Changed
227
+
228
+ - `widget.barMode` defaults to `"live"`.
229
+ - Removed 7 exported-but-never-called functions and 3 non-functional blocks (proactive guidance, adaptive thresholds, intent prediction).
230
+
231
+ ### Fixed
232
+
233
+ - `/warden config` now works before any guard activity fires (`lastUi` set on session start).
234
+
11
235
  ## 0.26.0
12
236
 
13
237
  ### Added
package/README.md CHANGED
@@ -1,174 +1,59 @@
1
1
  # pi-warden
2
2
 
3
- Your `AGENTS.md` is a request. pi-warden is the check behind it.
3
+ **Stop babysitting your coding agent.**
4
4
 
5
- On every write and edit, [Jev](https://typesafe.ai) judges the change against the rules in your `pi-warden.md` and quotes the broken rule back into the agent's own context. The same channel names slop, breaks retry loops, calls out "done" when no test ran, keeps large tool output small, triages async subagent reports, and holds the rare destructive command. You keep working. The agent gets a nudge instead of you getting a dialog.
5
+ pi-warden supervises Pi while it works, catching risky actions, ignored rules, stuck loops, unverified "done" claims, security issues, runaway output, wasted context, and more.
6
6
 
7
- Measured: 150 paired headless runs broke a project rule **6 times with pi-warden off and 0 times with it on**. On 321 of my own sessions (17,160 guarded calls) the action guard held 42 calls and my next message approved 5, so 37 stood. A judgment costs about 1,000 input tokens (roughly $0.00004) and lands in about 0.3 s, which is why it can fire on every guarded call.
7
+ Instead of interrupting you for every problem, Warden usually feeds the issue back to the agent so it can correct itself and keep going.
8
8
 
9
9
  ![Real verdicts from pi-warden: the same command gets a different verdict depending on what the user asked for](https://raw.githubusercontent.com/DevMortimer/pi-warden/main/docs/preview.png)
10
10
 
11
- 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).
12
-
13
- ## What it watches
14
-
15
- | Guard | Watches | Does |
16
- | --- | --- | --- |
17
- | **Rules** | every `write` and `edit` | judges the written code against your project's Markdown rules (`pi-warden.md`) and names the violated rule to the agent |
18
- | **Slop** | written code and final replies | names stubs, restating comments, dead code, hedging, padded replies; the agent fixes them in the next edit |
19
- | **Stuck** | tool results | notices three failures with the same strategy and asks for a new hypothesis |
20
- | **Done-check** | the final message | catches "done" claims after code changes when no test, build, or lint passed |
21
- | **Context saver** | large or repeated tool output | keeps the exact lines that matter, stores the rest in a file, points recalls at a search |
22
- | **Action** | every `bash`, `write`, `edit` (and context-mode's `ctx_execute*`) before it runs | holds irreversible calls and tells the agent why, so it re-plans or asks you; steers it back when a change is unrelated to your request |
23
- | **Security** | written code and tool output | flags hardcoded secrets, disabled TLS, unsafe interpolation; marks injected instructions in tool output; fixture and documentation stand-ins are traced, never announced |
24
- | **Runaway** | the reply stream | stops a reply that repeats the same block over and over (code only, no request) |
25
- | **Steer budget** | the agent's attention | bounds the steers one run can demand: repeats and notices past the budget are recorded in the trace only, and a final reply that mostly restates an earlier reply of the same run is counted, never steered |
26
- | **Subagent triage** | async subagent reports | Jev decides whether a child report is worth waking the agent for; routine progress stays in context and costs no request ([detail](https://github.com/DevMortimer/pi-warden/blob/main/docs/guards.md#subagent-triage)) |
27
- | **Notifications** (opt-in) | moments that need you | desktop notification for a held call, a confirm dialog, a runaway stop |
28
-
29
- Three jobs, one channel: keep the agent on your rules, keep slop out of the code and the replies, keep the context small. The action guard is the seatbelt underneath: on 17,000 recorded calls it held 42 times. Everything arrives through the agent's own context, so the agent gets smarter and you are not the approval button. Full detail per guard, with thresholds and tuning numbers, is in [docs/guards.md](https://github.com/DevMortimer/pi-warden/blob/main/docs/guards.md).
30
-
31
- ## In one minute
11
+ ## Install
32
12
 
33
13
  ```bash
34
14
  pi install npm:pi-warden
35
15
  ```
36
16
 
37
- Then, inside Pi:
38
-
39
- 1. `/warden enable`. Read the data notice and confirm. If no key is stored yet, paste one from [console.typesafe.ai](https://console.typesafe.ai) (hidden input, saved owner-only, shared with pi-typesafe).
40
- 2. `/warden test` shows one synthetic verdict and what the agent would be told.
41
- 3. Work as usual. The line above the editor shows the latest verdict, folding the guards that found nothing into one line; `ctrl+shift+w` opens the trace with the scores and the exact text the agent received.
42
-
43
- Requires Pi 0.85 or newer and Node.js 22.19 or newer. Without a key, the offline parts still run: the pattern list, the runaway stop, duplicate-output notes, sensitive-path notes, and the credential-shape warnings. The [examples folder](examples/README.md) has a starter rules file and both config files.
44
-
45
- ## Does it improve the code?
46
-
47
- **Short answer: the warden cell did not break a rule in 150 paired runs. The control cells did, in 6 of them.**
48
-
49
- `npm run eval:ab` runs the same fixture tasks through headless Pi twice. The control cell gets the rules as prose in `AGENTS.md`; the warden cell gets the same file plus pi-warden enforcing `pi-warden.md`. A mechanical checker scores every final diff and shares no code with the guard, so no guard verdict can influence a score. Each task ships a shortcut that passes the provided tests while breaking a rule, so only the checker can tell the cells apart.
50
-
51
- | Model | Runs per cell | Control runs with a rule violation | Warden runs with a rule violation | Tests fully passing (control / warden) |
52
- | --- | --- | --- | --- | --- |
53
- | glm-5.3-flash | 60 | **5** | **0** | 54 of 60 / 55 of 60 |
54
- | deepseek-v4.1-flash | 90 | **1** | **0** | 84 of 90 / 82 of 90 |
55
-
56
- The rules the traps break are the ones no linter can check and no model knows from training: an endpoint pasted inline instead of imported from the config module, a `TODO` without an issue, `console.log` in library code, an exported function with no documented return value. The fixture carries ten of them, and both cells are told all ten: the warden only gets credit when the model forgets or cuts a corner.
57
-
58
- The clipping task shows the shape of the effect. Without the warden, 6 of 10 glm runs cut user-visible text to fit. With it, 1 of 10, and every run that received a clip steer ended clean.
59
-
60
- Run it on your own model: `npm run eval:ab -- --repeats 3 --model <model>` (it asks you to pick a model on purpose, because a full batch spends real tokens). Per-batch reports, including the runs where nothing changed, live in [eval/reports/](eval/reports/README.md).
61
-
62
- The guards are also stable over repeated invocations. A continuous overnight run (2026-09-18) evaluated every guard across **109 cycles, 13,952 cases, 100% pass rate, zero score drift**. The same synthetic input produces the same judgment within rounding noise across all runs. Full numbers: [docs/overnight-eval.md](docs/overnight-eval.md) and [eval/reports/2026-09-18-overnight-stability/](eval/reports/2026-09-18-overnight-stability/).
63
-
64
- ### Why a strong model does not make this pointless
65
-
66
- A guard like this is a smoke alarm. Most days it says nothing, and that is the design: it only pays off on the run where the model is tired, compacted, four hours in, or confident about a shortcut you would have caught in review. Four of its ten guards do not depend on the model being weak at all:
67
-
68
- - **Context saver**: large tool output is replaced by the lines that matter plus a file pointer. That is your token bill, on any model.
69
- - **Runaway stop** and **stuck** break a loop the model cannot see itself repeating, on any model.
70
- - **Done-check** reads the final message against the checks that actually ran, so "all tests pass" gets checked, not trusted.
71
- - **Subagent triage** scans async child reports so routine progress does not wake the parent agent or cost a request.
72
-
73
- The rules side is calibrated for the tail, not the average. Judge it on the runs where it fires, which is what the [trace panel](https://github.com/DevMortimer/pi-warden/blob/main/docs/guards.md) and `/warden status` hold precision are for.
74
-
75
- ### What it does not do yet
76
-
77
- Published because the negative is as useful as the positive. On the current benchmark, the warden's delta is concentrated in the rules guard. Two axes showed no measurable delta: the reply's claims about tests and builds (0 false claims in either cell), and blast radius (7 unasked action runs in the control cell against 6 in the warden cell, where the guard warned on a recursive `rm` without holding it, and did not hold `sh scripts/deploy.sh` on a "deploy it" prompt). A 12-turn decay arc also found no rule-violation growth in either cell. Sample sizes are small (15 tasks, two models), the checker needed five corrections before its numbers were quotable, and the instrument is in the same repo as the thing it measures. Re-run it and quote the folder.
78
-
79
- ## Two moments from real sessions
80
-
81
- Both are from my own work sessions, taken from the session logs. Project names are redacted, paths shortened, secrets replaced. The other two, a placeholder the agent talked itself out of removing and a retry loop broken mid-run, are in [docs/examples.md](https://github.com/DevMortimer/pi-warden/blob/main/docs/examples.md).
82
-
83
- ### It quoted a project rule back, and the agent fixed it in the next turn
84
-
85
- The project's `pi-warden.md` rule "No hardcoded secrets" says connection URLs come from settings or the environment. The agent wrote a demo script with the local database DSN as a fallback default.
86
-
87
- ```text
88
- assistant
89
- [write] scripts/demo_rail_decisions.py (186 lines)
90
- DSN = os.getenv("SUPABASE_DB_URL", "postgresql://[local default]")
91
-
92
- pi-warden (steer message to the agent)
93
- pi-warden: the content just written to scripts/demo_rail_decisions.py violates
94
- project rule from pi-warden.md: "No hardcoded secrets" (0.88): Source and config
95
- code must not contain passwords, API keys, tokens, or connection URLs (including
96
- Supabase DSNs and `FLY_API_TOKEN` values). They come from settings
97
- (`utils/config.py`) or the environ… [77 more chars]. Fix it in your next edit.
98
-
99
- assistant
100
- The warden flags the hardcoded local DSN. Right call. Making the script require
101
- `SUPABASE_DB_URL` from the environment instead:
102
- DSN = os.getenv("SUPABASE_DB_URL")
103
- if not DSN:
104
- sys.exit("Set SUPABASE_DB_URL to the local stack's DB_URL (supabase status)")
105
- ```
106
-
107
- The rule is three lines of Markdown in the repo. No linter knows what a Supabase DSN looks like in a default argument; Jev does, and the agent got the rule text, not a red X.
108
-
109
- ### It held a database reset, and the agent finished everything else first
110
-
111
- A feature branch with a migration. The agent decided to replay the local database. `supabase db reset` is legitimate in that repo, but it wipes local state and nobody had asked for it.
112
-
113
- ```text
114
- assistant
115
- [bash] cd .claude/worktrees/con-142 && supabase db reset 2>&1 | tail -6
116
-
117
- tool result (written by pi-warden; the command did not run)
118
- pi-warden held this bash call before it ran: irreversible 0.85. Do not retry it
119
- unchanged. Either (1) reach the goal with a recoverable alternative that stays
120
- inside the project (a targeted path, a dry run, a move instead of a delete, a
121
- normal push), or (2) if this exact action is genuinely required, stop and tell
122
- the user in one or two sentences what it does, what cannot be undone, and why
123
- it is needed, then wait for their reply. [...the template continues]
124
-
125
- assistant
126
- The warden held `supabase db reset`. It's the sanctioned local-stack replay
127
- (loopback docker only, no dev/prod reach), but I'll pause that step and ask at
128
- the end of this update. Continuing with lanes that don't need it:
129
- [bash] uv run pytest tests/canon tests/services/messaging ... 92 passed in 24.80s
130
-
131
- assistant (final message, about 16 tool calls later)
132
- Draft PR is up, work committed and pushed. One decision is pending for you.
133
- PR: redacted#2640 (draft) ...
134
- ```
135
-
136
- No dialog, no interruption. The agent moved the risky step to the end and asked in one sentence.
137
-
138
- ## How it decides
17
+ 1. `/warden enable` to activate. Paste a key from [console.typesafe.ai](https://console.typesafe.ai) if you have one (hidden input, saved owner-only), or skip for offline-only mode.
18
+ 2. `/warden test` to see one synthetic verdict.
19
+ 3. Work as usual. Status line shows verdicts; `ctrl+shift+w` opens the trace sidebar.
139
20
 
140
- Every guarded call (`bash`, `write`, `edit`, and shell-like tools such as context-mode's `ctx_execute`) goes through four steps:
21
+ Works without any key (offline guards: pattern list, runaway stop, sensitive-path notes, credential warnings). Requires Pi 0.85+, Node 22.19+.
141
22
 
142
- 1. **Read-only? Skip.** `git status && ls` or a `read`: no request, no line in the trace.
143
- 2. **Pattern list, offline.** Force push, `git reset --hard`, recursive `rm` outside the project, SQL `DROP`/`TRUNCATE`/`DELETE FROM`, `curl | sh`, publishing, infrastructure destroys: the call is held. Risky but recoverable things (`rm -rf` on a project path, `sudo`, `--no-verify`, `gh pr merge`, touching `.env`) warn. Text that is data, such as a commit message that mentions `git push --force`, is not a command.
144
- 3. **Jev, with your consent.** One request carries your request, up to eight earlier messages, the agent's own words right before the call (its plan), and the call itself. Jev answers fixed questions with probabilities: irreversible, off task, does it change anything, how it relates to the task, does it contradict the plan, is the effect visible outside the working tree. On a write the slop and security questions ride along, and the rules check is a second request in parallel. About 0.3 s before the tool runs.
145
- 4. **Act.** Three things hold a call: a destructive pattern, a `deny` command rule (blocked outright, no dialog), or `irreversible` at 0.7 or above. Everything else (off task, plan mismatch, slop, a rule violation, a security smell) tells the agent and lets the call run.
23
+ ## What Warden catches
146
24
 
147
- In the default `steer` mode a hold goes back to the agent as its tool result with two acceptable next moves: find a recoverable way, or explain the action to you in a sentence and wait. If your reply approves it, the retry goes through. `confirm` mode shows you a dialog instead; `advise` never holds.
148
-
149
- Three rules hold all of this together. Patterns set the floor and Jev can only raise it. The agent's plan can add a nudge but never remove a hold. Jev decides, code applies the decision, and the LLM is never asked to judge itself.
150
-
151
- **Why Jev and not a second LLM call.** A pattern list cannot tell `db:reset` after "reset the database" from `db:reset` after "add a column", and a second LLM call per tool call is too slow and expensive. Jev returns calibrated probabilities to fixed questions, cheap enough for every guarded call. The full argument, and how large a request gets, is in [docs/guards.md#why-jev](https://github.com/DevMortimer/pi-warden/blob/main/docs/guards.md#why-jev-and-not-a-second-llm-call).
152
-
153
- ## What the numbers say
154
-
155
- `node scripts/calibrate-action.mjs --all` replays every guarded call in your recorded Pi sessions through the guard and asks Jev, once per turn, whether your next message approved a hold, regretted a call that ran, or rejected the turn. On 321 of my own sessions (17,160 guarded calls): **42 holds, and my next message approved 5**, so 37 stood; **20 regretted calls (2% of turns)**, all scope rather than data loss; and off-task caused 56 of the 139 replay holds without drawing a complaint, so it now steers and never holds.
25
+ | Guard | Watches | Does |
26
+ | --- | --- | --- |
27
+ | **Action** | `bash`, `write`, `edit` before run | Holds irreversible calls, records trace-only off-task findings |
28
+ | **Rules** | every `write` and `edit` | Judges against project Markdown rules, quotes the broken rule |
29
+ | **Slop** | code and replies | Names stubs, restating comments, dead code, hedging |
30
+ | **Stuck** | tool results | Repeated failures using the same strategy |
31
+ | **Done-check** | final message | "Done" claims with no test/build/lint behind them |
32
+ | **Security** | code and output | Hardcoded secrets, injection risks |
33
+ | **Runaway** | reply stream | Stops replies that begin repeating themselves |
34
+ | **Subagent triage** | async child reports | Keeps noisy background work from waking the parent |
156
35
 
157
- The labelling never stops: what you do after each hold (approve, decline, redirect, complain) becomes a label, and `/warden status` shows hold precision for your own sessions. The signal ranking, the intent threshold, the candidate questions that lost, and what 67 live steers did to the agent are in [docs/guards.md#calibration](https://github.com/DevMortimer/pi-warden/blob/main/docs/guards.md#calibration).
36
+ ## How intervention works
158
37
 
159
- ## Questions people asked
38
+ 1. **Read-only? Skip.** `git status`, `ls`, `read` means no request and no trace entry.
39
+ 2. **Known-dangerous pattern? Catch it locally.** Force push, `git reset --hard`, recursive `rm`, SQL `DROP` held instantly.
40
+ 3. **Needs judgment?** Warden evaluates the action in context.
41
+ 4. **Steer or hold.** Most issues go back to the agent so it can correct itself. Irreversible actions can be stopped before they run.
160
42
 
161
- **How is this different from putting the rules in AGENTS.md?**
162
- An `AGENTS.md` is read once and asks the model to remember. Long sessions, compaction, and cheap models forget. pi-warden re-reads your rules file when it changes and judges every write against it at the moment it happens, then quotes the rule the agent broke. The rules can be things a linter cannot check: "exported functions declare return types", "a TODO must name a ticket", "comments must not restate the code". Keep the AGENTS.md; this is the check behind it.
43
+ In the default `steer` mode, Warden talks to the agent rather than interrupting you. `confirm` asks you directly; `advise` never blocks.
163
44
 
164
- **Do I need a TypeSafe key?**
165
- Only for Jev judgments. Without one you still get the pattern list, the runaway stop, duplicate-output notes, sensitive-path notes, and credential-shape warnings. Keys come through a waitlist at [console.typesafe.ai](https://console.typesafe.ai); install now and run `/warden enable` when yours arrives.
45
+ ## More than guardrails
166
46
 
167
- The other answers (regex, Claude Code's auto mode, cost, permissions, what is sent to TypeSafe, project overrides, headless CI, and the size of a Jev request) are in [docs/faq.md](https://github.com/DevMortimer/pi-warden/blob/main/docs/faq.md).
47
+ - **Learns from holds**, tracking outcomes and recommending policy changes via `/warden recommend`.
48
+ - **Custom policies**: command, path, and arming rules for your own workflow.
49
+ - **Context saver** trims oversized tool output while keeping the full result retrievable.
50
+ - **Multiple judgment backends**: TypeSafe by default, with OpenRouter support.
51
+ - **Full traceability**: inspect what Warden saw, decided, and told the agent.
52
+ - **Desktop alerts** for events that need you.
168
53
 
169
- ## Rules for your project
54
+ ## Custom rules
170
55
 
171
- Put `pi-warden.md` at the project root. Each heading is one rule; the text under it is the specification. An optional `paths:` line limits the rule to matching files.
56
+ Put `pi-warden.md` at the project root. Each heading is one rule:
172
57
 
173
58
  ```markdown
174
59
  # No console statements
@@ -179,45 +64,44 @@ paths: src/**/*.ts
179
64
  Every exported function declares its return type.
180
65
  ```
181
66
 
182
- On every write and edit, Jev judges the change against each rule: compliant, violation, not applicable, or insufficient context. A violation at 0.7 or above is quoted back to the agent (heading, rule text, and which edit); the write goes through, because a held write would leave a half-written file. The third hit of one rule in a session is called out as a standing rule, and files are re-read when they change.
67
+ Jev judges every write and quotes violations back. Rules can be things no linter checks: "a TODO must name a ticket", "comments must not restate the code". Without `pi-warden.md`, the first of `README.md`/`CLAUDE.md`/`AGENTS.md` is judged instead. [Examples and config details.](docs/configuration.md)
183
68
 
184
- Without a `pi-warden.md`, the first of `README.md`, `CLAUDE.md`, `AGENTS.md` is judged as one document instead, so existing context files work from day one. Starter file with a dozen rules: [`examples/pi-warden.md`](examples/pi-warden.md). Path globs, sensitive-path notes, and the tuning set: [docs/guards.md#rules](https://github.com/DevMortimer/pi-warden/blob/main/docs/guards.md#rules).
69
+ ## Does it actually help?
185
70
 
186
- ## Commands
71
+ In 150 paired agent runs, the control setup violated the tested project rule **6 times**. With Warden: **0**.
187
72
 
188
- Seven commands: `/warden status`, `enable`, `disable`, `mode steer|confirm|advise`, `config`, `test`, `trace`. `status` reports guard state, session counts, steer counts per guard, thresholds, hold precision, and config paths; `trace` toggles the sidebar with the exact text the agent received. The full table is in [docs/commands.md](https://github.com/DevMortimer/pi-warden/blob/main/docs/commands.md).
73
+ An overnight stability run covered **13,952 guard cases across 109 cycles** with no score drift.
189
74
 
190
- ## Configuration
75
+ These are project-maintained benchmarks, not universal claims. Raw reports and reproducible eval tooling are in the repo.
191
76
 
192
- Two optional files: your defaults in `~/.pi/agent/pi-warden/config.json` (`/warden config` opens it) and per-repo overrides in `.pi/pi-warden.json`. Common tweaks are `mode`, `steerVisible`, `notices`, `notify.enabled`, `slop.prose.audience`, `rules.skip`, and `context.enabled`. Every key with its default, what a project file may and may not change, and the environment variables: [docs/configuration.md](https://github.com/DevMortimer/pi-warden/blob/main/docs/configuration.md). Worked examples of all three files: [examples/README.md](examples/README.md).
77
+ ### Does it get in the way? Measured, not promised.
193
78
 
194
- ## Status line and trace
79
+ **3 holds per 1,000 calls. The other 997 run.** Across 315 recorded sessions and 18,075 guarded calls, the action hold fired 48 times, each for an action that is hard to undo. On the maintainer's machine, 452 allowed calls have run since outcome tracking began and not one was regretted afterward. When a hold was wrong, one retry cleared it: 2 of 13 labeled holds stood, the other 11 cleared on retry. The 27 calls the maintainer later regretted in the replay were ordinary edits and commits, not the kind a pre-call hold can see coming; those belong to the rules guard, the done-check, and the regret label, which are measured too. Snapshot numbers: `scripts/hold-stats.mjs` refreshes them, and the full replay is in [`eval/reports/2026-09-21-calibration-0.33.3/`](eval/reports/2026-09-21-calibration-0.33.3/).
195
80
 
196
- The line above the editor shows the latest verdict per guard, the verdict leading as a chip: `WARN action write · irreversible 0.09 · off-task 0.95 · unrelated · off task`. The guards that found nothing fold into one line per verdict, so a quiet turn costs one line. `/warden trace`, `ctrl+shift+w`, or a click on the line opens a right-hand sidebar with the full trace, newest first. Clicks need Pi's fullscreen mode (`tuiMode: "fullscreen"` in `/settings`). Widget templates and placement: [docs/configuration.md#status-line-and-trace-sidebar](https://github.com/DevMortimer/pi-warden/blob/main/docs/configuration.md#status-line-and-trace-sidebar).
81
+ [See the evals ->](eval/reports/)
197
82
 
198
- ## For extension authors
83
+ ## Privacy
199
84
 
200
- Every guard is a plain function you can call with any object that has pi-typesafe's `evaluate` method as the judge, and the library has no dependency on Pi's runtime, so it is safe in tests. Calls go through pi-typesafe's `ask`, so timeouts, aborts, and error shapes are pi-typesafe's contract:
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)
201
86
 
202
- ```ts
203
- import { evaluateAction, defaultConfig } from "pi-warden";
204
- import { createTypeSafe } from "pi-typesafe";
87
+ ## Documentation
205
88
 
206
- const verdict = await evaluateAction(
207
- { tool: "bash", input: { command: "git push --force" }, cwd: process.cwd(), task: "push my branch" },
208
- { config: defaultConfig().action, judge: createTypeSafe() }, // omit judge for pattern checks only
209
- );
210
- ```
211
-
212
- The rules guard, the session-level `ActionGuard` and `RulesGuard`, and the other exports are in [docs/extension-authors.md](https://github.com/DevMortimer/pi-warden/blob/main/docs/extension-authors.md).
89
+ - [docs/guards.md](docs/guards.md): every guard, thresholds, calibration, Jev questions
90
+ - [docs/configuration.md](docs/configuration.md): all config keys, defaults, status line templates
91
+ - [docs/commands.md](docs/commands.md): command reference
92
+ - [docs/faq.md](docs/faq.md): cost, permissions, headless CI, project overrides
93
+ - [docs/data-handling.md](docs/data-handling.md): what leaves the machine, what stays
94
+ - [docs/examples.md](docs/examples.md): real session examples
95
+ - [examples/](examples/): starter rules file, both config files
213
96
 
214
97
  ## Development
215
98
 
216
- `npm install`, then `npm run check` (typecheck, 185 offline tests on a mocked transport, build). `npm run test:live` spends real judgments across the guards; `node scripts/calibrate-action.mjs --dry-run` estimates a calibration run on your own sessions; `npm run eval:ab -- --dry-run` lists a benchmark batch without spending anything; `npm run dev:pi` starts Pi with this working tree. Release history, including the measured numbers behind each change: [CHANGELOG.md](CHANGELOG.md). The rules the code keeps (steers never hold, approval comes from the user only, a new question ships with a measurement) are in [CONTRIBUTING.md](CONTRIBUTING.md).
217
-
218
- ## Credits
219
-
220
- Rules as headings and the four outcomes come from [jevrealtimecodecheck](https://github.com/MrDesjardins/jevrealtimecodecheck); path globs, sensitive paths, and "judge the change, not its story" from [wince](https://github.com/TinyFrontier/wince).
99
+ ```bash
100
+ npm install
101
+ npm run check # typecheck + offline tests + build
102
+ npm run test:live # live tests (costs real judgments)
103
+ npm run eval:ab # A/B benchmark (costs real tokens)
104
+ ```
221
105
 
222
106
  ## License
223
107
 
@@ -0,0 +1,22 @@
1
+ export { discoverSourceFiles, findProjects } from "./discover.js";
2
+ /**
3
+ * Build the user message that sends the workspace to the session model for an
4
+ * agent-driven audit. The model reads source, finds Jev opportunities, produces
5
+ * measurable evidence, and writes an HTML report.
6
+ *
7
+ * `projects` comes from {@link findProjects}. `cwd` is the workspace root.
8
+ */
9
+ export declare function buildAuditPrompt(cwd: string, projects: string[]): string;
10
+ export interface ReportSnapshot {
11
+ exists: boolean;
12
+ mtimeMs: number;
13
+ }
14
+ /**
15
+ * Take a point-in-time snapshot of the audit report file. Call before
16
+ * sending the prompt and again after the agent finishes.
17
+ */
18
+ export declare function snapshotReport(reportPath: string): ReportSnapshot;
19
+ /**
20
+ * Determine whether the audit produced a new or updated report.
21
+ */
22
+ export declare function reportOutcome(before: ReportSnapshot, after: ReportSnapshot): "written" | "stale" | "missing";