protect-context 0.0.0-stage → 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 XJTLUmedia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,620 @@
1
- # Temporary Holding Version
1
+ # protect-context · Agent Guardrail Suite
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Durable context, real evidence, and a brake pedal for AI coding agents.**
4
+
5
+ `protect-context` keeps the rules, decisions, and proof-of-work an agent needs in
6
+ front of it — then makes scope creep, unverified claims, and repeated failures
7
+ visible *before* they cost you a session.
8
+
9
+ <p align="center">
10
+ <a href="./LICENSE"><img alt="license: MIT" src="https://img.shields.io/badge/license-MIT-green"></a>
11
+ <a href="https://pi.dev/packages"><img alt="pi-package" src="https://img.shields.io/badge/pi--package-gallery-8a2be2"></a>
12
+ <a href="pyproject.toml"><img alt="python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-blue"></a>
13
+ <img alt="enforcement: advisory-first" src="https://img.shields.io/badge/enforcement-advisory--first-lightgrey">
14
+ <img alt="runtime dependencies: zero" src="https://img.shields.io/badge/npm%20runtime%20deps-0-success">
15
+ </p>
16
+
17
+ ![protect-context injects a session brief — contract, decisions, corrections, loops, last evidence — before the agent starts working](./preview.png)
18
+
19
+ One project-local `.agent-guard/` store · three interfaces backed by the same
20
+ logic:
21
+
22
+ | Interface | Use it for | Runtime |
23
+ |---|---|---|
24
+ | **pi extension** | Automatic brief injection, status widget, loop blocking, diff warnings, risky-command confirmation | TypeScript, loaded by pi via jiti |
25
+ | **MCP server** | Portable model/host access to the same tools | Python 3.10+, `fastmcp` |
26
+ | **Python CLI** | People, scripts, CI, and hosts without MCP | Python 3.10+ |
27
+
28
+ The suite is **advisory-first**. It records evidence and makes unsafe or
29
+ repetitive behavior visible; it is not a sandbox and does not prove that a
30
+ change is correct. [`SPEC.md`](./SPEC.md) is the frozen v1.0 contract.
31
+
32
+ ![Architecture: the pi extension, MCP server, and Python CLI share a project-local .agent-guard store for rules, journal entries, and evidence; only pi injects briefs automatically](./docs/images/shared-store.svg)
33
+
34
+ ## Contents
35
+
36
+ - [Why this exists](#why-this-exists)
37
+ - [What it does](#what-it-does)
38
+ - [Quick start](#quick-start)
39
+ - [Install the pi extension](#install-the-pi-extension)
40
+ - [Install the MCP server](#install-the-mcp-server)
41
+ - [Install the Python CLI](#install-the-python-cli)
42
+ - [From source](#from-source)
43
+ - [A 30-second tour](#a-30-second-tour)
44
+ - [Interface comparison](#interface-comparison)
45
+ - [Reference](#reference)
46
+ - [pi commands and tools](#pi-commands-and-tools)
47
+ - [MCP tools](#mcp-tools)
48
+ - [CLI](#cli)
49
+ - [Configuration](#configuration)
50
+ - [Environment variables](#environment-variables)
51
+ - [Runtime store and concurrency](#runtime-store-and-concurrency)
52
+ - [The 12 failure modes and what ships](#the-12-failure-modes-and-what-ships)
53
+ - [How it works](#how-it-works)
54
+ - [Session lifecycle](#session-lifecycle)
55
+ - [The brief](#the-brief)
56
+ - [Evidence and loop detection](#evidence-and-loop-detection)
57
+ - [Scope audit](#scope-audit)
58
+ - [Trust and provenance](#trust-and-provenance)
59
+ - [Context budget](#context-budget)
60
+ - [Development and validation](#development-and-validation)
61
+ - [Troubleshooting](#troubleshooting)
62
+ - [Limitations and security boundary](#limitations-and-security-boundary)
63
+ - [Design sources](#design-sources)
64
+ - [Non-goals](#non-goals)
65
+ - [License](#license)
66
+
67
+ ## Why this exists
68
+
69
+ Long agent sessions fail in boring, expensive ways:
70
+
71
+ - the constraints you stated at minute two are gone by minute forty;
72
+ - "done" is claimed with no command output behind it;
73
+ - a "quick fix" quietly rewrites six unrelated files;
74
+ - decisions get re-litigated because nothing recorded the reasoning;
75
+ - the same failing command is retried until the context window dies.
76
+
77
+ `protect-context` treats those as **state** problems, not intelligence problems.
78
+ A durable store plus three small habits — brief before work, evidence after
79
+ work, journal when something is decided — removes most of the failure surface
80
+ without turning the agent into a different agent.
81
+
82
+ ## What it does
83
+
84
+ | Primitive | What it gives you | Where you meet it |
85
+ |---|---|---|
86
+ | **Brief** | Contract, recent trusted decisions and corrections, open loops, and the last evidence ID, injected before the agent starts a turn | Automatic in pi; `orientation`, `status`, `guard_journal` elsewhere |
87
+ | **Orientation** | Bounded repo digest: tree, languages, and conventions files (`CLAUDE.md`, `AGENTS.md`, `CONVENTIONS.md`, `README.md`, `README`) | Injected once per digest in pi; `orientation` in CLI/MCP |
88
+ | **Evidence ledger** | Every verification run recorded as `ev-NNNN` with exit code, duration, command, and a bounded output digest | `guard_verify`, `protect-context verify`, `guard_evidence` |
89
+ | **Loop brake** | Identical failing commands are fingerprinted; the Nth attempt is blocked instead of re-run | Hard block in pi and the CLI; reported through MCP |
90
+ | **Scope audit** | `git` diff stats over tracked, staged, and bounded untracked work, with `ok` / `warn` / `hard` verdicts | Widget and `status` in pi; `diff`, `guard_diff_audit` |
91
+ | **Journal** | Durable `decision` / `correction` / `risk` / `note` entries with tags and trust levels | `guard_note`, `protect-context note`, `guard_journal` |
92
+ | **Risky-command confirm** | Nine catastrophic shell patterns get one grouped confirmation instead of none | pi, when a UI is available |
93
+ | **Preflight** | Tool presence and versions plus Git worktree detection | `preflight`, `guard_preflight` |
94
+ | **Context budget** | Watermarks on context usage and hard caps on every injected block | Widget, `budget`, `guard_budget` |
95
+
96
+ ## Quick start
97
+
98
+ Python 3.10+ is required for the CLI and MCP server. Node 22.19+ is required
99
+ only to validate the TypeScript extension from a checkout — pi supplies the
100
+ extension's runtime modules, and the extension has **zero npm runtime
101
+ dependencies**.
102
+
103
+ ### Install the pi extension
104
+
105
+ Installed from npm. The package declares `"pi": { "extensions":
106
+ ["./extension/index.ts"], "image": "./preview.png" }` and the `pi-package`
107
+ keyword required by the Pi package gallery:
108
+
109
+ ```sh
110
+ pi install npm:protect-context
111
+
112
+ # Try it for one run without adding it to settings
113
+ pi -e npm:protect-context
114
+ ```
115
+
116
+ Or copy/symlink the checkout into a trusted extension directory:
117
+
118
+ ```sh
119
+ # Personal
120
+ mkdir -p ~/.pi/agent/extensions
121
+ cp -r extension ~/.pi/agent/extensions/protect-context
122
+
123
+ # Project-local (read after the project is trusted)
124
+ mkdir -p .pi/extensions
125
+ cp -r extension .pi/extensions/protect-context
126
+ ```
127
+
128
+ Start a session and you get a single-line status widget, a brief before the
129
+ first turn, and five slash commands. No configuration file is needed: the
130
+ extension seeds `.agent-guard/config.json` with the frozen defaults on first
131
+ session start.
132
+
133
+ ### Install the MCP server
134
+
135
+ For pi with `pi-mcp-adapter`, add `.mcp.json` at the project root:
136
+
137
+ ```json
138
+ {
139
+ "mcpServers": {
140
+ "protect-context": {
141
+ "command": "/absolute/path/to/protect-context/.venv/bin/python",
142
+ "args": ["-m", "protect_context.mcp_server"]
143
+ }
144
+ }
145
+ }
146
+ ```
147
+
148
+ The same stdio shape works in any generic MCP host. Set `PC_SESSION_ID` when
149
+ several host sessions need distinct evidence and loop views:
150
+
151
+ ```json
152
+ {
153
+ "mcpServers": {
154
+ "protect-context": {
155
+ "command": "/absolute/path/to/protect-context/.venv/bin/python",
156
+ "args": ["-m", "protect_context.mcp_server"],
157
+ "env": { "PC_SESSION_ID": "my-session" }
158
+ }
159
+ }
160
+ }
161
+ ```
162
+
163
+ The MCP surface exposes the nine frozen v1.0 tools plus two compatible read
164
+ surfaces added to close the original reachability gaps (`guard_journal` and
165
+ `guard_orientation`). Tools carry conservative MCP annotations; in particular
166
+ `guard_verify` is marked destructive and open-world because it can run any
167
+ shell command. Annotations are host hints, not enforcement — configure the host
168
+ to require human approval for that tool.
169
+
170
+ ### Install the Python CLI
171
+
172
+ ```sh
173
+ pip install protect-context # or: pipx install protect-context
174
+ protect-context init # seed .agent-guard/ in the current project
175
+ protect-context status
176
+ protect-context verify "pytest -q"
177
+ ```
178
+
179
+ The CLI and MCP server are distributed through PyPI; the npm package ships only
180
+ the pi extension. Both read the same `.agent-guard/` directory, so a project can
181
+ use the extension in pi and scripted verification in CI at the same time.
182
+
183
+ ### From source
184
+
185
+ ```sh
186
+ # Python package, CLI, MCP server, and Python tests
187
+ python3 -m venv .venv
188
+ .venv/bin/python -m pip install -e ".[dev]"
189
+
190
+ # Reproducible extension development checks (dev dependencies only)
191
+ npm ci --ignore-scripts
192
+ npm run check
193
+ ```
194
+
195
+ If the machine's default Node is older than 22.19, run the same checks with an
196
+ ephemeral Node 22 binary after `npm ci`:
197
+
198
+ ```sh
199
+ npx -y node@22 node_modules/typescript/bin/tsc --noEmit -p extension/tsconfig.json
200
+ npx -y node@22 --experimental-strip-types --test extension/test/*.test.ts
201
+ ```
202
+
203
+ ## A 30-second tour
204
+
205
+ Excerpts from a real session in this repository.
206
+
207
+ Ask for the current state:
208
+
209
+ ```console
210
+ $ protect-context status
211
+ PC status · session default · git 91fad4d
212
+ Δdiff +88/-8 · 6 files · verdict ok
213
+ loops: none
214
+ contract 12 constraints
215
+ conventions: README.md
216
+ Nudge: verify changes with guard_verify and cite the evidence id before claiming success.
217
+ ```
218
+
219
+ Record proof instead of asserting it:
220
+
221
+ ```console
222
+ $ protect-context verify "npm run check"
223
+ PC evidence ev-0005 · exit 0 · 1440ms
224
+ …output digest…
225
+ Nudge: cite evidence ids, not memory, when claiming success.
226
+ ```
227
+
228
+ Re-run the same failing command a third time and the brake engages:
229
+
230
+ ```console
231
+ $ protect-context verify "npm test"
232
+ PC verify blocked · npm test · 3 prior failures
233
+ Nudge: change approach or gather new information — do not re-run unchanged.
234
+ ```
235
+
236
+ What pi injects before the agent starts working (sections elided):
237
+
238
+ ```console
239
+ contract:
240
+ 01-obey-instructions: Obey the owner's explicit instructions and stated constraints exactly; when instructions conflict, ask instead of guessing.
241
+ 02-evidence-before-success: Never claim success without citing a fresh verification run (evidence id or pasted output) for the exact claim.
242
+ …
243
+ decisions:
244
+ - Use the atomic-mkdir lease, not fcntl alone; Python must interoperate with the extension. [locking,spec]
245
+ last verify: ev-0005 exit=0 'npm run check' 1.4s
246
+ ```
247
+
248
+ And the always-on one-line widget:
249
+
250
+ ```console
251
+ PC Δ+88L/6f 0loop ev5 ctx8%
252
+ ```
253
+
254
+ ![Illustrative pi widget: Δ+123L/4f means 123 changed lines across 4 files, 1loop means one command at the blocking threshold, ev2 counts two verification runs this session, and ctx41% shows context usage](./docs/images/status-widget.svg)
255
+
256
+ ## Interface comparison
257
+
258
+ | Capability | pi extension | MCP server | CLI |
259
+ |---|---|---|---|
260
+ | Brief injected automatically before a turn | yes (change-triggered) | — | — |
261
+ | Orientation digest (tree, languages, conventions) | injected once per digest | `guard_orientation` | `orientation` |
262
+ | Verification evidence (`ev-NNNN`) | `guard_verify`, `/guard-verify` | `guard_verify` | `verify` |
263
+ | Loop blocking | blocks the command | `guard_loop_check` reports and advises | `verify` refuses; `loops` exits 1 |
264
+ | Scope audit | session-start baseline + warnings | `guard_diff_audit` (explicit baseline) | `diff --baseline REF` |
265
+ | Journal write/read | `guard_note`, `/guard-note` | `guard_note`, `guard_journal` | `note`, `journal` |
266
+ | Trusted re-injection of decisions | yes, after change or compaction | read views only | read views only |
267
+ | Contract management | `/guard-contract` | `guard_contract` | `contract list\|add\|remove` |
268
+ | Risky-command confirmation | one grouped confirm | host approval policy | — |
269
+ | Status widget | yes | — | `status` |
270
+
271
+ ## Reference
272
+
273
+ ### pi commands and tools
274
+
275
+ Slash commands:
276
+
277
+ | Command | Action |
278
+ |---|---|
279
+ | `/guard-status` | Compact session status: diff verdict, loops, evidence, contract, context |
280
+ | `/guard-contract` | `list` · `add <text>` · `remove <id>` — edit the persistent contract |
281
+ | `/guard-verify` | Run a command through the evidence ledger |
282
+ | `/guard-note` | Journal a `decision` · `correction` · `risk` · `note` |
283
+ | `/guard-report` | Full session summary for handoff or review |
284
+
285
+ Model-callable tools:
286
+
287
+ | Tool | Arguments | Notes |
288
+ |---|---|---|
289
+ | `guard_verify` | `command`, `timeout_s` (default 180, clamped 1–900) | Records `ev-NNNN`; refusal on a blocked loop |
290
+ | `guard_note` | `type`, `text`, optional `tags` | Forced to the `agent` trust level |
291
+ | `guard_contract_check` | `summary` | Deterministic keyword coverage checklist, never a proof |
292
+ | `guard_status` | — | Same data that feeds the widget |
293
+
294
+ ### MCP tools
295
+
296
+ | Tool | Annotation class | Purpose |
297
+ |---|---|---|
298
+ | `guard_status` | local, initializing read | Session status digest |
299
+ | `guard_verify` | **shell execution** (destructive, open-world) | Run a command, record evidence |
300
+ | `guard_evidence` | read-only | Recent verification entries |
301
+ | `guard_contract` | local mutation | `list` · `add` · `remove` |
302
+ | `guard_note` | local append | Journal an entry (tagged `agent`) |
303
+ | `guard_journal` | read-only | Recent journal entries, optionally filtered by type |
304
+ | `guard_loop_check` | local, initializing read | Failing-command streaks and advice |
305
+ | `guard_diff_audit` | local, initializing read | Diff stats and verdict vs thresholds |
306
+ | `guard_preflight` | local, initializing read | Tool/version and worktree check |
307
+ | `guard_budget` | read-only | Context-budget watermarks |
308
+ | `guard_orientation` | read-only | Repo digest and conventions files |
309
+
310
+ ### CLI
311
+
312
+ Every command returns `0` for OK, `1` for a triggered warning or failed
313
+ verification, and `2` for an input/environment error.
314
+
315
+ ```sh
316
+ protect-context init
317
+ protect-context status
318
+ protect-context verify "pytest -q" [--timeout 180]
319
+ protect-context evidence [--limit 5]
320
+ protect-context contract list
321
+ protect-context contract add "Keep the public API stable"
322
+ protect-context contract remove ID
323
+ protect-context note correction "use rg, not grep" --tags search
324
+ protect-context journal [--limit 5] [--type decision]
325
+ protect-context orientation
326
+ protect-context loops
327
+ protect-context diff [--baseline REF]
328
+ protect-context preflight
329
+ protect-context budget
330
+ ```
331
+
332
+ ### Configuration
333
+
334
+ The extension seeds `.agent-guard/config.json` on first session start; `init`
335
+ does the same for CLI/MCP-first use. Unknown, wrongly typed, fractional, or
336
+ out-of-range values fall back to the frozen default.
337
+
338
+ | Key | Default | Meaning |
339
+ |---|---:|---|
340
+ | `loop_block_after` | `3` | Block the Nth identical failing command attempt |
341
+ | `diff_warn_lines` | `400` | Changed-line warning threshold |
342
+ | `diff_hard_lines` | `800` | Changed-line hard-level threshold |
343
+ | `diff_warn_files` | `10` | Changed-file warning threshold |
344
+ | `ctx_warn_pct` | `70` | First context-usage warning watermark |
345
+ | `brief_mode` | `"changed"` | Inject only when the brief digest changes; `"always"` injects every turn |
346
+ | `risk_confirm` | `true` | Ask once before a matching risky pi bash command when UI is available |
347
+ | `preflight_tools` | `git`, `python3`, `pip`, `node`, `npm`, `rg`, `pytest` | Executables checked by preflight |
348
+
349
+ Numeric thresholds may be set to zero; the extension records an explicit first
350
+ notification so a zero watermark does not silently disable alerts. Preflight
351
+ checks at most 20 configured tool entries. If more are configured, it lists the
352
+ omitted count and returns a warning instead of claiming the environment is
353
+ fully checked. Version probes have a two-second per-tool timeout, share a
354
+ 15-second overall deadline, and decode at most 4 KiB of output; a missing
355
+ version is reported as an incomplete preflight.
356
+
357
+ The nine catastrophic patterns that trigger the pi confirmation are frozen and
358
+ deliberately narrow, so benign lookalikes stay silent:
359
+
360
+ `rm -rf /` · `rm -rf ~` · `git push --force` (not `--force-with-lease`) ·
361
+ `git reset --hard` · `mkfs … /dev/…` · `dd … of=/dev/…` ·
362
+ `chmod -R 777 /` · `curl … | sh` · `sudo rm`
363
+
364
+ ### Environment variables
365
+
366
+ | Variable | Effect |
367
+ |---|---|
368
+ | `PC_SESSION_ID` | Names the session used for evidence and loop views, so parallel hosts do not share a view |
369
+
370
+ ### Runtime store and concurrency
371
+
372
+ Runtime files stay in the frozen `.agent-guard/` layout and are gitignored:
373
+
374
+ `config.json` · `contract.json` · `journal.jsonl` · `events.jsonl` ·
375
+ `verify_runs.jsonl` · `state.json`
376
+
377
+ Python and TypeScript use the same atomic-directory lease at
378
+ `.agent-guard/.lock.d`, with an owner token, PID/hostname checks, bounded
379
+ waiting, and conservative stale recovery. Python also takes its legacy local
380
+ `fcntl.flock` inside that lease. Evidence-ID allocation, JSONL appends, and
381
+ contract read-modify-write operations hold the shared lease, preventing
382
+ duplicate IDs and lost contract updates across runtimes. JSON file replacement
383
+ uses unique temporary files.
384
+
385
+ The lease follows the atomic `mkdir` design used by mature lock libraries while
386
+ remaining dependency-free and interoperable with Python. A crashed same-host
387
+ owner is reclaimable after the stale interval; a stale foreign-host owner fails
388
+ closed because process liveness cannot be proved safely without an external
389
+ coordinator. Recovery retains a small identity-bound `.lock.d.stale.*`
390
+ directory as a durable race guard; it does not block new writers and lives
391
+ inside the already-ignored runtime store. Contract mutations also fail closed
392
+ when owner rows are malformed, preserving the original file for repair.
393
+
394
+ ## The 12 failure modes and what ships
395
+
396
+ | # | Failure mode | Shipped mitigation | Enforcement |
397
+ |---|---|---|---|
398
+ | 1 | Violating instructions | Persistent contract; provenance-aware, change-triggered brief injection; seed-removal refusal at the MCP boundary | Context/advisory |
399
+ | 2 | Claiming success without verification | Verification ledger, evidence IDs, status/evidence views, failure digests | Recorded evidence; not semantic proof |
400
+ | 3 | Editing too much / scope creep | Git diff audit over tracked, staged, and bounded non-ignored untracked work; safe explicit baselines | Warn/advisory |
401
+ | 4 | Misunderstanding the repo | Worktree-root-aware bounded orientation digest in pi, CLI, and MCP; conventions-file discovery | Automatic context + explicit read |
402
+ | 5 | Plausible but subtly wrong code | Deterministic contract checklist, verification gate, test-required seed | Heuristic/advisory |
403
+ | 6 | Losing earlier context | Persistent journal; recent trusted decisions re-injected after change and compaction; CLI/MCP read views | Automatic context + explicit read |
404
+ | 7 | Repeating corrected behavior | Bounded tagged corrections; trusted corrections re-injected; model-authored entries retained for owner review | Automatic context + trust boundary |
405
+ | 8 | Large diffs | Configurable line/file warning and hard-level notifications, status verdict, widget | Warn/advisory |
406
+ | 9 | Repetitive debugging loops | Shared bash/evidence streak reconstruction; third identical failing command blocked by default in pi, CLI, and MCP | Hard block at configured threshold |
407
+ | 10 | Environment/tool failures | Tool/version and Git-worktree preflight with warning exit outside a repository | Diagnostic |
408
+ | 11 | Permission interruptions | One grouped pi confirmation for the frozen risky-command patterns; environment preflight | Interactive confirmation where UI exists |
409
+ | 12 | Token/API cost and latency | Bounded brief sections, journal/contract text, evidence digests, untracked reads, output previews, and context watermarks | Automatic budgeting/advisory |
410
+
411
+ "Shipped" means the mitigation is reachable and tested. It does not turn an
412
+ advisory signal into a security or correctness guarantee.
413
+
414
+ ## How it works
415
+
416
+ ### Session lifecycle
417
+
418
+ The extension is a thin event layer over the shared store. Nothing leaves your
419
+ machine: there are no network calls and no LLM calls in the guardrail logic.
420
+
421
+ | Event | What happens |
422
+ |---|---|
423
+ | `session_start` | Seed config if missing, scan orientation, freeze the diff baseline, start the status widget |
424
+ | `before_agent_start` | Build the brief; inject orientation and/or brief as a hidden message when their digests changed |
425
+ | `tool_call` (bash) | Match the nine risky patterns → one grouped confirm; check the loop fingerprint → block at the threshold |
426
+ | `tool_execution_start` / `tool_execution_end` | Record bash outcomes and verify results into the shared loop state and evidence ledger |
427
+ | `turn_end` | Refresh the widget: diff stats, open loops, evidence count, context percentage |
428
+ | `session_compact` | Re-inject trusted context so compaction cannot silently erase it |
429
+ | `session_shutdown` | Flush state and clear the widget |
430
+
431
+ ### The brief
432
+
433
+ The brief is deliberately tiny — a persistent prompt that grows without bound is
434
+ its own failure mode. Within the frozen 12-line constraint cap:
435
+
436
+ 1. newest **owner** constraints are selected first,
437
+ 2. then the frozen seed constraints,
438
+ 3. then other entries.
439
+
440
+ An agent-authored constraint therefore cannot crowd out a seed. Below the
441
+ contract it carries up to 3 recent trusted decisions, up to 3 trusted
442
+ corrections, up to 2 open loops, and the last evidence ID with its exit code,
443
+ duration, and a truncated command. `brief_mode: "changed"` (default) injects
444
+ only when the rendered digest actually changed, so a quiet turn costs nothing.
445
+
446
+ ### Evidence and loop detection
447
+
448
+ `guard_verify` runs your command with the caller's privileges, spools output to
449
+ temporary files to bound RAM, and records one append-only entry: ID, exit code,
450
+ duration, command, and a digest of the output. IDs are allocated inside the
451
+ shared lease, so parallel pi and CLI processes never collide.
452
+
453
+ Loop detection normalizes a command (whitespace collapsed, including inside
454
+ quoted strings) into a fingerprint, then counts consecutive failures of that
455
+ fingerprint across both bash and `guard_verify`. At `loop_block_after` the
456
+ attempt is refused, with the prior failure count and a change-strategy nudge —
457
+ the single cheapest intervention in the suite.
458
+
459
+ ![Verification workflow: run a command, persist its evidence, and cite the result. With the default threshold, two failures of the same normalized command block its third attempt; only that command succeeding resets its streak](./docs/images/evidence-loop.svg)
460
+
461
+ ### Scope audit
462
+
463
+ Diffs are computed with `git` porcelain/numstat over tracked, staged, and
464
+ bounded non-ignored untracked files, then compared against the configured line
465
+ and file thresholds to produce `ok`, `warn`, or `hard`. The pi extension freezes
466
+ its baseline at session start, so committing mid-session does not reset the
467
+ scope; CLI and MCP default to `HEAD` and accept an explicit baseline.
468
+
469
+ Untracked content reads are capped at 1,000 files, 1 MB per file, and 8 MB
470
+ total, and stop once the hard line threshold is known. A truncated scan is
471
+ reported as a bounded scan, promoted to at least a warning (CLI exit `1`), and
472
+ its count treated as a lower bound — the widget renders it as `Δ≥`.
473
+
474
+ ### Trust and provenance
475
+
476
+ Direct CLI and pi slash-command contract writes are treated as owner input.
477
+ MCP/model journal writes are forcibly tagged `agent`: they stay inspectable
478
+ through journal views but are not re-injected as trusted decisions or
479
+ corrections. CLI and pi slash-command journal entries are trusted. Untagged
480
+ legacy entries are treated as trusted for backward compatibility, because the
481
+ frozen journal schema has no dedicated author field.
482
+
483
+ This is a provenance convention, not authentication. A model that already has
484
+ shell access can invoke the CLI and appear on that trusted path; use host-level
485
+ tool permissions when the distinction must be a security boundary.
486
+
487
+ All newly stored contract, journal, and tag text is normalized to one bounded
488
+ line, and legacy values are normalized again when rendered. This prevents
489
+ terminal-line forging and unbounded persistent prompt growth; it does not make
490
+ untrusted prose safe to follow.
491
+
492
+ ### Context budget
493
+
494
+ Every injected and printed block has a cap: brief sections, journal and contract
495
+ text, evidence digests, orientation output, and previews. Context-watermark
496
+ notifications fire at `ctx_warn_pct` and above, and the widget always shows the
497
+ current percentage. The result is a guardrail layer whose own cost stays
498
+ constant while a session grows.
499
+
500
+ ## Development and validation
501
+
502
+ ```sh
503
+ .venv/bin/pytest -q # Python suites
504
+ npm run typecheck # extension types (tsc --noEmit)
505
+ npm test # extension unit tests (node --test)
506
+ .venv/bin/python -m compileall -q src # byte-compile check
507
+ .venv/bin/python -m build --outdir /tmp/protect-context-dist
508
+ ```
509
+
510
+ `npm run check` runs the typecheck and the extension tests together. The
511
+ definition-of-done checks are run twice consecutively before release. The dated
512
+ audit records the observed counts, integration probes, and package smoke test
513
+ instead of leaving stale numbers in this README.
514
+
515
+ | Path | Contents |
516
+ |---|---|
517
+ | `src/protect_context/` | Store, contract, journal, verify, loops, diff scope, orientation, preflight, budget, report, CLI, MCP server |
518
+ | `extension/` | pi entry point (`index.ts`) and pi-import-free `lib/` modules |
519
+ | `tests/` | pytest suites, including a fastmcp in-memory client smoke over every MCP tool |
520
+ | `extension/test/` | `node --test` suites for fingerprinting, risk patterns, loops, brief digest, widget, git parsing, config |
521
+ | `SPEC.md` | Frozen v1.0 contract — structure, defaults, and definition of done |
522
+
523
+ ## Troubleshooting
524
+
525
+ | Symptom | Likely cause | Fix |
526
+ |---|---|---|
527
+ | No brief appears in pi | Digest unchanged (`brief_mode: "changed"`) and nothing new to say | Expected on quiet turns; set `brief_mode: "always"` to see it every turn |
528
+ | `Δ?` in the widget | Not a Git worktree, so no diff baseline | Run inside a repository, or use the CLI with an explicit `--baseline` |
529
+ | `Δ≥` in the widget | Bounded untracked scan hit a cap | Treat the count as a lower bound; narrow the scan or review scope manually |
530
+ | Loop never blocks | Commands differ (paths, flags) so fingerprints differ | Compare with `loops` / `guard_loop_check`; fingerprints intentionally ignore whitespace only |
531
+ | `PC_SESSION_ID` changes nothing in evidence | It scopes ledger *views*, not `state.json` | Expected: state is project-global by design |
532
+ | MCP tool missing in host | Server not started with the venv interpreter | Point `command` at `.venv/bin/python` and pass `-m protect_context.mcp_server` |
533
+ | Extension does not load | Node older than 22.19 or pi not restarted | Check `pi list`, then restart; use `pi -ne` to start without extensions |
534
+ | Contract edit rejected over MCP | Removing a frozen seed constraint | Seeds are protected; use the CLI or a pi slash command as the owner |
535
+
536
+ ## Limitations and security boundary
537
+
538
+ #### Shell execution
539
+
540
+ - `guard_verify` deliberately invokes a shell with the caller's privileges. It
541
+ can modify files, access the network, or disclose data. The pi layer asks for
542
+ confirmation only when one of the frozen regex patterns matches; the MCP layer
543
+ relies on host approval. Neither is a shell parser or a sandbox.
544
+ - MCP annotations are untrusted hints. A client may ignore them. Do not expose
545
+ `guard_verify` to an untrusted model without a real host sandbox/approval
546
+ policy.
547
+
548
+ #### Evidence
549
+
550
+ - An evidence record proves only that one command returned one exit code. It is
551
+ not cryptographically bound to a commit/worktree, has no freshness policy, and
552
+ does not prove the claimed behavior.
553
+
554
+ #### Scope
555
+
556
+ - The pi extension freezes its diff baseline at session start, so committing
557
+ mid-session does not reset its scope. CLI/MCP audits default to current
558
+ `HEAD`; pass them an explicit baseline for cross-commit scope. Binary files
559
+ count as files but zero lines.
560
+ - Untracked-file content reads are capped at 1,000 files, 1 MB per file, and
561
+ 8 MB total, and stop once the hard line threshold is known. Output explicitly
562
+ reports a bounded scan, promotes it to at least a warning/CLI exit 1, and
563
+ treats the truncated count as a lower bound.
564
+
565
+ #### Determinism
566
+
567
+ - The frozen brief carries at most 12 constraints. Overflow is visible, and
568
+ owner constraints are prioritized, but hidden constraints are not enforced by
569
+ context injection.
570
+ - Frozen fingerprint normalization collapses whitespace everywhere, including
571
+ inside quoted shell strings. Semantically different commands can therefore
572
+ share a loop fingerprint.
573
+ - The deterministic contract checker is keyword coverage, not program analysis.
574
+ Treat every `addressed` result as a review prompt, not proof.
575
+
576
+ #### Store
577
+
578
+ - `state.json` is project-global. Atomic replacement prevents torn JSON, but
579
+ simultaneous sessions can still overwrite notification/session state; use
580
+ `PC_SESSION_ID` to separate ledger views, not state files.
581
+ - Commands and the last 2,000 output characters are stored verbatim. There is
582
+ no automatic secret detector or redactor; never put credentials in commands or
583
+ verification output.
584
+ - JSONL ledgers have no automatic retention or rotation. Output views stay
585
+ bounded, but long-lived stores grow on disk and full session reconstruction
586
+ becomes progressively more expensive; archive runtime state deliberately.
587
+ - Python verification bounds RAM by spooling output to temporary files, but a
588
+ very noisy command can consume temporary disk until it exits. On Windows,
589
+ killing the direct process cannot guarantee that every descendant exits.
590
+
591
+ ## Design sources
592
+
593
+ The implementation was checked against primary or professional sources:
594
+
595
+ - Git's stable porcelain/other-file interfaces for non-ignored untracked
596
+ enumeration: [git-status](https://git-scm.com/docs/git-status) and
597
+ [git-ls-files](https://git-scm.com/docs/git-ls-files).
598
+ - Pi's documented lifecycle, tool, context, and UI APIs:
599
+ [Pi extension documentation](https://pi.dev/docs/latest/extensions).
600
+ - The mature `proper-lockfile` design, which uses atomic `mkdir` rather than
601
+ `O_EXCL`: [moxystudio/node-proper-lockfile](https://github.com/moxystudio/node-proper-lockfile).
602
+ The package itself was not added because the frozen extension requirement is
603
+ zero runtime dependencies and the lock must interoperate with Python.
604
+ - Operational-safety evidence motivating explicit constraints, failure
605
+ transparency, and safe-halt behavior: [What Breaks When LLMs Code?](https://arxiv.org/abs/2605.30777)
606
+ (ASE 2026), plus evidence-conditioned execution results in
607
+ [Preventing Premature Commitment in Coding Agents](https://arxiv.org/abs/2607.28815).
608
+ - MCP's own warning that annotations are risk vocabulary rather than
609
+ enforcement: [Tool Annotations as Risk Vocabulary](https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/).
610
+
611
+ ## Non-goals
612
+
613
+ No network or LLM calls are made by the guardrail logic itself. It is not a
614
+ sandbox, policy engine, auto-fixer, secret manager, or replacement for pi/MCP
615
+ host permissions. It complements those controls with durable context, evidence,
616
+ telemetry, and narrowly scoped blocking.
617
+
618
+ ## License
619
+
620
+ MIT — see [`LICENSE`](./LICENSE). Copyright (c) 2026 XJTLUmedia.