ccppccpp home
Browse the documentation

Plugins · CPO

Builtins

Ready-made checks for when: size limits, banned commands, counters, path globs and protected paths.

A builtin is used as when: { builtin: <name>, with: { … }, where: <CEL> }. It holds when it finds what it looks for, and reports facts that where, the body ({{ facts.* }}) and the effects can read.

BuiltinEventsParametersFacts
max-linesfile.writemax, onlyGrowthlines, linesBefore
max-bytesfile.writemax, onlyGrowthbytes, bytesBefore
command-banshell.execcommands, allowInprogram, args, via
counteranykey, max, scopecount
path-matchanyglobpath
protected-pathsfile.write, shell.execpathspath, protected, program
ts-no-any, commentsfile.writeonlyNew…see Code rules
lsp-diagnostics, lsp-queryfile.write…severity, query…see Language servers

Size limits: max-lines, max-bytes

when: { builtin: max-bytes, with: { max: 4096, onlyGrowth: true } }

Both hold when the write leaves the file over max: lines (a trailing newline ends the last line) or UTF-8 bytes. linesBefore / bytesBefore measure the file before the write, null for a new file.

onlyGrowth: true judges the change, not the file: the write holds only if it ends over the limit and makes the file larger. A file already over the limit may shrink or keep its size.

Before → after, max: 4096defaultonlyGrowth
3193 → 4097holdsholds
new file → 5000holdsholds
5000 → 5100holdsholds
5000 → 4900holdsdoes not hold
3193 → 3500does not holddoes not hold

Content unknown before the write (shell redirections, tee) skips the check; what a shell wrote is checked once it ran. In CEL, the same measures are lines(s) and size(bytes(s)).

Banned programs: command-ban

.claude/policies/pnpm-only.policy.md
---
description: Install with pnpm
on: [shell.exec]
when: { builtin: command-ban, with: { commands: [npm, yarn], allowIn: [npx] } }
---
This project uses pnpm.

Holds when any parsed command runs one of commands, compared by program name, wherever it sits: after &&, in a pipeline, inside sudo, xargs or sh -c. allowIn lists enclosing programs under which the command is allowed. Combine with where to look at arguments:

when: { builtin: command-ban, with: { commands: [rm] }, where: "facts.args.exists(a, a == '-rf')" }

Counting: counter

on: [net.fetch]
when: { builtin: counter, with: { key: fetches, max: 20, scope: turn } }
then: [ask]

Adds one to state.<scope>.<key> (scope session by default, or turn, toolUse) each time it is evaluated, and holds once the count exceeds max. Put it last in an all so it only counts matching events.

Globs: path-match

when: { not: { builtin: path-match, with: { glob: ["**/*.gen.ts", "**/generated/**"] } } }

Holds when the event's project-relative path matches glob, one glob or a list.

Protected paths: protected-paths

when: { builtin: protected-paths, with: { paths: [infra/prod, ~/.config/acme] } }
then: [ask]

Holds when a write reaches one of paths (files or directories; absolute, ~/… or project-relative), and when a shell command may change one: a redirection, rm / mv / chmod -R on it or a directory above it, sed -i, git checkout -- <path>, a ccpp cpo profile command, or any argument of a program that is not a known reader (cat, grep…) or inspector (ls, stat…).

It is deliberately broad, so pair it with ask rather than deny where false alarms matter. CPO's own protection is built on it.

Edit this page on GitHub