# pi-ignore

> Make specific skills / MCP servers / tools **inactive** in specific directories or globally — like `.gitignore`, but for pi capabilities. Integrated at the front of pi's interception chain, it applies to **every skill's guard extension automatically, with zero changes and zero awareness** from them.

[中文文档](./README.md) · English

## Quick Start

```bash
# One-command install from npm (extensions + docs auto-registered;
# postinstall runs install.py --quiet so everything lands automatically)
pi install npm:pi-ignore

# Then create rules in your project:
echo "skill:wekan-task-manager" > .pi/.piignore   # skip wekan guard in this project
echo "node_modules/"              >> .pi/.piignore # guards skipped under node_modules
```

> **How it works**: the extension **self-loads the enforcement patch**
> (patch.mjs) at pi startup, so it works no matter how pi is launched
> (pnpm shim, npm bin, wrapper) — no PATH changes or shell restarts needed.
> `~/.pi/bin/pi` wrapper is optional belt-and-suspenders (preloads the
> patch before extensions even load).

Check: `python3 install.py --check` · Uninstall: `python3 install.py --uninstall`

## Using /piignore (interactive wizard)

After installing, **restart pi**, then type `/piignore` in the terminal to start
configuring — **all dialogs, no LLM involved**:

1. **Probe the current path**: auto-detects common ignore targets
   (`node_modules/`, `dist/`, `build/`, `__pycache__/`, `.venv/`, `.git/`,
   `*.log` …), available skills, MCP servers, and existing rules
2. **Wizard**: after confirming, pickers open for **multi-round multi-select**:
   - 📁 Directories/files (detected ones first, e.g. `node_modules/`)
   - 🛡 Skill ignores (e.g. `skill:wekan-task-manager`)
   - 🔌 MCP / tools (e.g. `mcp:github`)
   - ✍️ Custom rules (free-form input, e.g. `tool:edit`)
3. **Choose the tier**: project `.pi/.piignore` (current project only) or
   global `~/.pi/.piignore` (all projects)
4. **Preview & confirm**: rules are **appended, never overwriting** existing ones

Other subcommands:

```bash
/piignore check <path>      # is this path ignored?
/piignore check-tool <name> # is this tool disabled?
/piignore cleanup [skill]   # remove injection blocks from CLAUDE.md/AGENTS.md (default: skills covered by rules)
/piignore help              # help
```

> Arrow keys to select, Enter to confirm, Esc to cancel; if you don't want to
> configure anything, just answer "No" after the probe results.

### Injection blocking (channel ②)

Some skills (e.g. wekan-task-manager) write a startup instruction block into
project `CLAUDE.md` / `AGENTS.md`, which pi loads as system prompt on every new
session — runtime guard-skipping cannot stop that. For every skill covered by
an active `skill:xxx` rule, pi-ignore handles both channels:

1. **System-prompt filtering**: the `<!-- xxx:start -->…<!-- xxx:end -->` block
   is removed from the assembled system prompt (dynamic; restore by deleting the rule)
2. **File cleanup**: `/piignore cleanup` removes the injected blocks from
   CLAUDE.md/AGENTS.md

## Rules (.piignore examples)

Rules live in three tiers and support gitignore syntax (`*`, `**`, `/`
anchoring, negation) plus `skill:`/`mcp:`/`tool:` prefixes:

```
# ── 1. Path rules: hit → guards are SKIPPED (skill hooks never run) ──
node_modules/            # directory and everything under it (auto-recursive)
*.log                    # any .log at any depth
build/output.js          # anchored to project root only
**/test/fixtures/        # test/fixtures dirs at any depth

# ── 2. Negation (whitelist): re-enable a subtree under an ignored one ──
node_modules/            # ignore node_modules first
!node_modules/keep/      # then keep this subtree back under guard supervision

# ── 3. skill rules: the whole skill is disabled (guards skipped + SKILL.md unreadable) ──
skill:wekan-task-manager # e.g. wekan's mandatory card workflow is off in this project
skill:wekan-*            # glob: every wekan-* skill disabled

# ── 4. MCP / tool rules: hard-blocked (call returns {block}, fails the LLM) ──
mcp:github               # disable the whole github MCP server
mcp:github__search_repos # or target a single tool by its name
# one rule per line; write multiple prefixes as separate lines
```

**Priority**: project `.pi/.piignore` > global `~/.pi/.piignore` >
capability-owned (`<skill>/.piignore`); within the same tier later rules win;
`skill:`/`mcp:`/`tool:` (block) beat path rules (skip).

**Typical scenarios**:

```bash
# A: skip wekan's mandatory card workflow in one project
#    (the .gitignore equivalent of ignoring a "file"):
echo "skill:wekan-task-manager" > .pi/.piignore

# B: no guard noise under node_modules, in every project:
echo "node_modules/" >> ~/.pi/.piignore

# C: disable an MCP server you don't use, globally:
echo "mcp:github" >> ~/.pi/.piignore

# D: ignore a directory but keep one subtree supervised (whitelist):
printf 'node_modules/\n!node_modules/vendor-keep/\n' > .pi/.piignore
```

Inspect with `/piignore`, or probe a path with `/piignore check <path>`.

## How It Works

pi's extension interception chain is first-block-wins — a guard that returns
`{block:true}` short-circuits, and extensions cannot undo or skip other
handlers. pi-ignore preloads a monkey-patch via `node --import` that wraps
`ExtensionRunner.prototype.emitToolCall` and arbitrates **before every guard**:

- path hit → return `undefined` (guards never run; the tool call proceeds)
- `tool:` / `mcp:` rule → return `{block:true, reason}`
- `skill:` rule → skip all guards (an operation cannot be attributed to a
  specific skill, so it applies globally) and block reading that skill's
  SKILL.md

**Health check**: on every startup pi-ignore verifies the patch can take
effect — module loads, method exists, implementation fingerprint
(`result.block` branch), no double patch. Any mismatch prints a loud warning
to stderr and **fails open**: pi runs normally, rules just don't apply.
After a pi upgrade, run `python3 install.py --fix`.

```
scripts/piignore.py (parsing, zero deps)
      ↓ JSON + precompiled regexes
extensions/pi-ignore/arbiter.mjs (matching + mtime fingerprint cache + health check)
      ↓
extensions/pi-ignore/patch.mjs (--import preload patch)
      ↓
~/.pi/bin/pi (wrapper: node --import patch.mjs <real cli.js>)
extensions/pi-ignore/index.ts (/piignore command, status UI)
```

## Tests

```bash
python3 -m pytest tests/            # parser, 24 tests
node --test tests/arbiter.test.mjs  # JS arbitration, 10 tests
node --test tests/integration/      # real preload integration, 8 tests
node --test tests/e2e-real.mjs      # real pi runner E2E, 4 tests (run install.py first)
```

## Troubleshooting

- After a pi upgrade, stderr shows "pi-ignore patch 未生效" → `python3 install.py --fix`
- Rules not applying → run `/piignore` to inspect loaded rules and patch status
- Full removal → `--uninstall` + drop `~/.pi/bin` from your shell rc

## License

MIT — see [LICENSE](./LICENSE).
