Metadata-Version: 2.4
Name: picket
Version: 0.1.0
Summary: Hard rules for AI coding agents - one rulefile, compiled to native hooks, zero daemons.
Project-URL: Repository, https://github.com/KaiC5504/picket
Project-URL: Issues, https://github.com/KaiC5504/picket/issues
Author: KaiChuan
License-Expression: MIT
License-File: LICENSE
Keywords: ai-agents,claude-code,guardrails,hooks,safety
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# picket

[![ci](https://github.com/KaiC5504/picket/actions/workflows/ci.yml/badge.svg)](https://github.com/KaiC5504/picket/actions/workflows/ci.yml)

**Hard rules for AI coding agents — one rulefile, compiled to native hooks, zero daemons.**

Your agents ignore CLAUDE.md instructions some fraction of the time, because prompt
instructions are probabilistic. Their hooks fire deterministically — no probabilistic
instruction-following. picket compiles one
`picket.toml` (protected branches, protected paths, secret patterns, command deny-list)
into your agent's **native hook configuration**, then exits. No daemon, no proxy, no LLM
calls, no network. Blocked attempts land in a local JSONL audit log.

> *demo GIF placeholder — split terminal: left, agent runs `git push origin main` → ✗ BLOCKED
> `[protect-branch:main]`; right, agent runs `cat .env` → ✗ BLOCKED `[protect-path:.env*]`;
> bottom, `picket log` showing both attempts.*

## Quickstart

```console
uvx picket init      # detect agents, write picket.toml with safe defaults
uvx picket apply     # compile + install native hooks (preview first: --dry-run)
# your agent is now fenced - restart any session already running in this repo
uvx picket log       # see everything that got blocked
uvx picket unapply   # restore your config byte-for-byte
```

> Not on PyPI yet? Run straight from git:
> `uvx --from git+https://github.com/KaiC5504/picket picket init`

The defaults are useful with zero editing: protect this repo's default branch (push,
force-push, deletion), deny reading/editing **existing** `.env*` files and anything under
`~/.ssh/**`, deny recursive deletes that resolve outside the repo, deny force-pushes, and
deny writing well-known secret formats (AWS/GitHub/Slack/Google/Anthropic keys,
private-key blocks) into files or commands.

**v0.1 targets Claude Code.** Codex, Cursor, and Copilot CLI targets are next
(the compiler was built around a target interface for exactly that).

## Reversibility is the product

- `picket apply --dry-run` prints the exact unified diff of every file it would touch,
  and writes nothing.
- `apply` is idempotent — running it twice changes nothing.
- Your existing hooks are never modified: picket appends one clearly recognizable entry
  to the **project-scope** `.claude/settings.local.json` (hooks merge across scopes in
  Claude Code, so user-level and plugin hooks keep running untouched).
- Before the first modifying write, the prior file is backed up; `picket unapply`
  restores it **byte-for-byte**. If you edited the file after apply, unapply surgically
  removes only picket's entries and tells you why a byte-identical restore wasn't possible.
- If your settings file is malformed JSON, picket aborts and touches nothing.

## What it is (and is not)

picket is a **footgun guard, not a sandbox**. It deterministically blocks the accidents
agents actually commit — pushing to main, reading `.env`, `rm -rf` outside the repo,
pasting a key into a file — and writes an audit trail. It does not defend against
malicious code, and its shell-command scanning is honest best-effort (e.g. `cat .env` is
caught; an obfuscated `cat $(echo .env)` is not). Hooks fail open by design: if the hook
itself breaks, your agent keeps working and the error is logged. For hostile-code
scenarios, pair picket with OS-level sandboxing (e.g. Anthropic's sandboxing features) —
they solve different problems and compose well.

## How it compares

| | picket | [prempti](https://github.com/falcosecurity/prempti) | [claude-code-guardrails](https://github.com/rulebricks/claude-code-guardrails) | [claude-guardrails](https://github.com/dwarvesf/claude-guardrails) |
|---|---|---|---|---|
| Architecture | compiles to native hooks, then exits | Falco-engine interceptor in the loop | PreToolUse hook config | deny rules + hooks dotfiles |
| Runtime footprint | none | daemon/interceptor | none | none |
| Rule format | plain TOML | Falco YAML dialect | JSON/scripts | JSON/scripts |
| Agents | Claude Code (Codex/Cursor/Copilot planned) | Claude Code + Codex (exp.) | Claude Code | Claude Code |
| Audit log | local JSONL + `picket log` | yes (its strength) | no | no |
| Reversibility | dry-run diff + byte-exact unapply | n/a (runtime component) | manual | manual |
| Windows | first-class (no bash-isms; PowerShell dialect parsed) | POSIX-first | bash hooks | bash hooks |

All of these are good projects — prempti in particular validates the category and is the
right choice if you want a security-team-grade Falco pipeline. picket's bet is the
dev-first corner: one TOML file, native hooks, nothing running afterwards, works on the
machine you actually develop on (including Windows), and trivially reversible.

## Rule reference

```toml
version = 1

[branches]
protected = ["main"]        # push, force-push, and deletion denied
deny_force_push = true      # force-push to ANY branch denied

[paths]
protected = [".env*", "~/.ssh/**"]   # Read/Write/Edit denied + best-effort shell scan
allow = ["*.example", "*.sample", "*.template"]  # exceptions: templates stay usable
# no "/" -> basename match at any depth (like .gitignore); with "/" -> full glob, ** ok
# basename patterns guard existing files only, so `cp .env.example .env` still works;
# full-path patterns like ~/.ssh/** are absolute (nothing may be added there either)
# tradeoff, stated honestly: an agent MAY create a brand-new .env - only the secret
# scan applies to freshly created files; once the file exists it is protected

[secrets]
builtin_patterns = true     # AWS, GitHub, Slack, Google, Anthropic keys, private-key blocks
extra = []                  # your own regexes

[commands]
deny = []                   # glob on the full command string, e.g. "*npm publish*"
deny_recursive_delete_outside_project = true
```

Every blocked attempt appends one JSONL line to `.picket/audit.jsonl`:

```json
{"ts": "2026-07-21T14:55:23Z", "agent": "claude-code", "session_id": "...",
 "rule": "protect-branch:main", "tool": "PowerShell",
 "action": {"command": "git push origin main"}, "decision": "deny", "message": "..."}
```

Written content is never logged (it may contain the secret being blocked).

## Development

```console
uv sync && uv run pytest    # 100 tests, run on ubuntu + windows in CI
```

This repo is fenced with picket itself (`picket.toml` at the root).

MIT license.
