Skip to content

feat(pi): add a native Pi guard extension (companion of Graphify-Labs/graphify#4378) - #14

Closed
andrebrait wants to merge 2 commits into
v8from
feat/pi-guard
Closed

andrebrait wants to merge 2 commits into
v8from
feat/pi-guard

Conversation

@andrebrait

Copy link
Copy Markdown
Member

Companion PR (review-only)

Companion of Graphify-Labs#4378 (same head, same upstream base v8 = 7536dc0, identical diff), opened on our fork so our review bots can access it.

Summary

Graphify already installs a Pi skill (graphify pi install), but Pi has no PreToolUse hook, so the hook-guard policy that Claude Code, Codex and others get never runs there. This adds a native Pi extension that runs the installed graphify hook-guard before Pi's tool calls.

  • graphify/pi/ is a Pi package (package.json with a pi.extensions entry, plus index.ts) shipped inside the wheel and sdist. It imports only Node built-ins, so it loads straight from site-packages with no node_modules.
  • On tool_call for read, grep, find and search-style bash, it calls graphify hook-guard read|search with a Claude-shaped payload. A strict-mode (GRAPHIFY_HOOK_STRICT=1) denial becomes a {block, reason} result. Guidance is sent as one <system-reminder source="graphify"> steering message. All graph decisions stay in the existing Python policy.
  • graphify pi guard install|uninstall delegates to Pi's own pi install|remove <package dir>. graphify pi guard path prints the directory.
  • Safety bounds:
    • Only local targets inside the project are checked.
    • The hook uses the first graphify on an absolute PATH entry outside the project, so there is no project-local fallback.
    • Each call has a 256 KiB input cap, a 64 KiB output cap and a 2 s SIGKILL budget.
    • In-flight calls are cancelled on session navigation and new runs.
    • Every failure fails open.

Oh My Pi

Oh My Pi reads the same pi manifest key, so the same package loads there with omp plugin install "$(graphify pi guard path)". On OMP, guidance uses its passive tool-call context channel instead of a steering message. OMP's glob and ;-separated targets are handled too. This supersedes Graphify-Labs#3506, which shipped an OMP-only package.

Testing

  • tests/pi.test.ts (bun, 19 tests) drives the real extension against the real installed graphify CLI. It covers nudges, the strict denial, out-of-project and remote targets, ~/@ paths, PATH shadowing and symlinked executables, timeouts, output and input caps, cancellation on navigation, unmapped tools, and Pi vs OMP delivery. The new pi-guard CI job runs it.
  • tests/test_pi_guard_install.py covers pi guard path, delegation to pi install/pi remove with exit-code propagation, and a missing pi.
  • Full pytest suite: 7376 passed, 185 skipped.
  • Manual tests with a non-editable install of the built package:
    • Pi 0.85.1: graphify pi guard install registered the package. A Pi SDK session loaded it from that registration and got nudges for grep/find/bash/read and the one-time strict denial.
    • Oh My Pi 18.10.1: omp plugin install "$(graphify pi guard path)" gave the same results.

@github-actions

Copy link
Copy Markdown

Thanks for the pull request, @andrebrait. A maintainer will review it soon.

Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions.

A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic.

@andrebrait

Copy link
Copy Markdown
Member Author

@codex review

@andrebrait

Copy link
Copy Markdown
Member Author

@coderabbitai review

Pi has no PreToolUse hook, so the installed hook-guard policy never ran
there. graphify/pi/ ships a Pi package (Node built-ins only, no
node_modules) that runs `graphify hook-guard read|search` before Pi's
read, grep, find and search-style bash calls: strict-mode denials become
tool_call blocks, and guidance arrives as a graphify system reminder.
Only local targets inside the project are checked, the hook uses the first
`graphify` on PATH outside the project, and every call is bounded and
fails open.

`graphify pi guard install|uninstall` delegates registration to Pi's own
`pi install|remove <dir>`; `graphify pi guard path` prints the package
directory. Oh My Pi reads the same `pi` manifest key, so
`omp plugin install "$(graphify pi guard path)"` loads it there too,
where guidance uses OMP's passive tool-call context channel.
@andrebrait

Copy link
Copy Markdown
Member Author

@codex review

@andrebrait

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@andrebrait

Copy link
Copy Markdown
Member Author

@codex review

@andrebrait

Copy link
Copy Markdown
Member Author

@coderabbitai review

@andrebrait

Copy link
Copy Markdown
Member Author

Upstream shipped this in Graphify v0.9.89 as an authorship-preserving cherry-pick (Graphify-Labs#4378). Closing the review copy.

@andrebrait andrebrait closed this Oct 13, 2026
@andrebrait
andrebrait deleted the feat/pi-guard branch October 13, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant