Imported from code-highway-patrol/chp (
.codex-plugin/plugins/chp/skills/write-laws/SKILL.md). Install upstream withnpx skills add code-highway-patrol/chp --skill write-laws. Copyright stays with the author.
CHP Law Management
Create new laws and refine existing ones. CHP provides two layers of enforcement:
- Suggestive Layer - Context documents that guide you to follow rules
- Verification Layer - Programmatic checks that catch violations
Setup (First Time Only)
Before creating laws, ensure hooks are installed:
# Check if hooks are installed
bash commands/chp-hooks list
# If no hooks are installed, install them
bash commands/chp-hooks install
Auto-install: When using this skill, hooks will be auto-installed if not present.
Creating a Law
When you need to enforce a rule or standard in the repository, use the chp-law CLI:
# Interactive mode (will prompt for confirmation)
bash commands/chp-law create <law-name> --hooks=pre-commit,pre-push
# Non-interactive mode (for agents/automation)
bash commands/chp-law create <law-name> --hooks=pre-commit,pre-push --yes
Example: No API Keys Law
# Create the law
chp-law create no-api-keys --hooks=pre-commit,pre-push
# This creates:
# - docs/chp/laws/no-api-keys/law.json (metadata)
# - docs/chp/laws/no-api-keys/verify.sh (verification script)
# - docs/chp/no-api-keys.md (suggestive context)
Law Schema
A law file contains the following fields:
Required Fields:
id- Unique identifier for the lawintent- High-level description of what the law protectsviolations- Array of violation patterns withpattern,fix, andsatisfiesreaction- How to respond:"block","warn", or"auto_fix"
Scope Control:
include- Glob patterns of files/directories this law applies to (empty = all files)exclude- Glob patterns to exempt from this law (overrides include)
Metadata:
tags- Categories for organizing/filtering laws (e.g.,["security", "secrets"])priority- Higher priority wins when multiple laws conflict (default: 0)author- Law owner/teamdocumentation- URL or path to extended documentationversion- Semantic version for tracking law evolution
Lifecycle:
createdAt- ISO 8601 timestamp when law was createdupdatedAt- ISO 8601 timestamp when law was last updatedexpiresAt- ISO 8601 timestamp for temporary lawsenabled- Quick disable without deleting (default: true)
Conditions:
environment- Environments where law applies (e.g.,["production", "staging"])dependsOn- Other law IDs that must be satisfied first
Enforcement:
severity- Severity level:"error","warn", or"info"hooks- Array of hooks that trigger this law
Example Law with Scope Control
{
"id": "no-api-keys",
"name": "no-api-keys",
"intent": "Prevent API keys from being committed to the repository",
"violations": [
{
"pattern": "fileContains(/sk_|AIza|AKIA/, content)",
"fix": "Remove API key and use environment variable",
"satisfies": "!fileContains(/sk_|AIza|AKIA/, content)"
}
],
"reaction": "block",
"include": ["**/*.ts", "**/*.js", "**/*.json"],
"exclude": ["**/examples/**", "**/*.example.json"],
"tags": ["security", "secrets"],
"priority": 100,
"author": "security-team",
"documentation": "/docs/security/api-key-handling.md",
"version": "1.2.0",
"environment": ["production", "staging"],
"enabled": true,
"hooks": ["pre-commit", "pre-push", "pre-tool"],
"severity": "error"
}
Composing Laws from Atomic Checks
CHP laws are composed of atomic checks — individual verifiable units that each check one specific thing. Each check has a type, configuration, severity level, and failure message. Checks are declared in law.json and executed by shared checker scripts in core/checkers/.
Decompose Law Intent into Atomic Checks
When creating a law, break down what the user wants into individual verifiable units. Each unit should check ONE thing.
Example: "No console logging in production code"
Decompose into:
- No
console.log()— block - No
console.debug()— warn - No
console.error()in non-error-handling contexts — warn
Each becomes a separate check with its own severity.
Choose Check Types
| Type | When to use | Config fields | Example |
|---|---|---|---|
pattern |
String/pattern matching (secrets, debug statements, keywords) | pattern (regex) |
"pattern": "console\\.log\\(" |
threshold |
Measurable limits (file size, function length, complexity) | metric, max/min |
"metric": "function_line_count", "max": 50 |
structural |
Convention checks (test files exist, import rules, auth middleware) | assert (named assertion) |
"assert": "test_file_exists" |
agent |
Subjective judgment (meaningful names, clear intent, good abstractions) | prompt (question for AI) |
"prompt": "Are these variable names meaningful?" |
Choosing the right type:
- If you can grep for it →
pattern - If you can count it →
threshold - If it's a convention →
structural - If it requires judgment →
agent
Set Per-Check Severity Levels
Each check has its own severity:
block— commit rejected, operation blockedwarn— logged but passes (counts toward tightening)log— silent tracking only
Example mix:
{
"checks": [
{
"id": "no-console-log",
"type": "pattern",
"config": { "pattern": "console\\.log\\(" },
"severity": "block",
"message": "Use logger.info() instead of console.log()"
},
{
"id": "no-console-debug",
"type": "pattern",
"config": { "pattern": "console\\.debug\\(" },
"severity": "warn",
"message": "Prefer logger.debug() over console.debug()"
}
]
}
Create Laws with Checks
Using chp-law create with check flags:
# Create a law with a single check
chp-law create no-console-log \
--hooks=pre-commit,pre-push \
--check-type=pattern \
--check-pattern="console\.log\(" \
--check-severity=block \
--check-message="Use logger.info() instead"
# Create a law with multiple checks (call multiple times)
chp-law create no-logging \
--hooks=pre-commit,pre-push \
--check-type=pattern --check-pattern="console\.log\(" --check-severity=block \
--check-type=pattern --check-pattern="console\.debug\(" --check-severity=warn
Add checks to existing laws:
# Add another check to an existing law
chp-law update no-console-log \
--add-check \
--check-type=pattern \
--check-pattern="console\.error\(" \
--check-severity=warn
Update individual check properties:
# Escalate a check from warn to block
chp-law update no-console-log \
--check=no-console-debug \
--severity=block
# Adjust a threshold
chp-law update no-long-functions \
--check=function-length \
--config.max=60
Auto-Generated verify.sh
When you create or update a law with checks, the verify.sh script is auto-generated. You do NOT write verify.sh manually anymore.
The auto-generated verify.sh:
- Reads the
checksarray fromlaw.json - Dispatches each check to
core/checkers/<type>.sh - Collects results and exits 1 if any
block-severity check fails
Example auto-generated verify.sh structure:
#!/bin/bash
source "$(dirname "$0")/../../../core/check-runner.sh"
run_checks "$LAW_NAME" "$HOOK_TYPE" "$@"
The check-runner.sh script handles all check execution. You only need to:
- Declare checks in
law.json - Use
chp-law createorchp-law updateto add them
Available Checkers
Located in core/checkers/:
pattern.sh— Regex matching against staged diff or filesthreshold.sh— Metric counting and comparisonstructural.sh— Convention assertionsagent.sh— Subjective AI judgment
Each checker implements the interface: check_<type> <hook_type> <config_json> <context>
Implementing the Verification
NOTE: With atomic checks, verify.sh is auto-generated. You do NOT write it manually. However, you MUST still follow the Research-First Protocol below when designing checks.
Research-First Protocol
Before writing a single line of verify.sh, you MUST complete these steps in this exact order.
Step 1: Read docs/chp/LAW-PATTERNS.md.
This file contains ready-made templates organized by detection type. You must read the relevant section before writing anything.
| If the law involves... | Read this section | Lines to jump to |
|---|---|---|
| String matching (secrets, debug statements, keywords) | Section 1: Regex/Shell | docs/chp/LAW-PATTERNS.md line 27 |
| Code structure (nesting, complexity, unused imports) | Section 2: AST-Based | docs/chp/LAW-PATTERNS.md line 252 |
| Existing tools (ESLint, Prettier, Semgrep, tsc) | Section 3: Delegated Tools | docs/chp/LAW-PATTERNS.md line 416 |
| Context-aware rules (allow in tests, conditional enforcement) | Section 4: Hybrid Patterns | docs/chp/LAW-PATTERNS.md line 539 |
If you find a matching template, stop here — use it. Do not write your own logic when a tested template exists.
Step 2: Read existing laws for prior art.
Run this to see what already exists:
bash commands/chp-law list
Then read the closest match. Here are the current production laws you can learn from:
| Law | What it demonstrates | File to read |
|---|---|---|
no-api-keys |
Multi-pattern regex, git staged + pre-tool contexts, temp file handling | docs/chp/laws/no-api-keys/verify.sh |
no-console-log |
Git diff filtering, file type exclusion, violation reporting | docs/chp/laws/no-console-log/verify.sh |
If a similar law exists, copy its structure. Change only the detection patterns — not the boilerplate around them.
Step 3: Check for overlap with existing laws.
Before implementing, grep the proposed detection patterns against all existing verify.sh scripts. If an existing law already checks for the same pattern, surface it to the user and ask whether to proceed.
Run this for each proposed pattern:
grep -rn 'PROPOSED_PATTERN' docs/chp/laws/*/verify.sh
Also check law.json intent fields for semantic overlap:
jq -r '.intent' docs/chp/laws/*/law.json 2>/dev/null
If overlap is found:
Present the overlapping law(s) to the user before continuing:
"This looks similar to the existing law
no-console-log, which already checks forconsole\.log. Do you want to:
- Extend the existing law with additional patterns
- Create a separate law anyway"
Wait for the user's decision. If they choose to extend, switch to Refining Existing Laws. If they confirm the new law is different, proceed.
If no overlap: move to Step 4.
Step 4: Research the detection method.
This step is only needed if Steps 1 and 2 did not give you a working approach. If you are unsure about regex patterns, encoding, tool flags, or AST queries — you must research before implementing.
For regex/encoding questions, test against real input:
# Test if a regex actually matches what you think it does
echo "sample text HERE" | grep -P 'YOUR_REGEX'
# For Unicode ranges (Chinese, emoji, RTL), use perl — grep -P Unicode support varies by platform
echo "中文内容" | perl -ne 'while (/[\x{4e00}-\x{9fff}]/g) { print "$&\n" }'
# Check what tools are available before depending on them
which perl jq grep sed 2>/dev/null
For tool delegation questions, check what's installed:
# Does this project have ESLint/Prettier/TypeScript?
ls -1 package.json .eslintrc* .prettierrc* tsconfig.json 2>/dev/null
# What ESLint rules are available?
npx eslint --print-config src/index.ts 2>/dev/null | jq '.rules | keys'
You are FORBIDDEN from guessing at:
- Regex patterns for things you haven't tested
- Unicode character ranges
- Tool CLI flags you haven't verified
- File paths or extensions that might not exist
When in doubt, use a delegated tool (ESLint, Semgrep, tsc) instead of hand-rolling detection.
Step 5: Confirm before writing.
You must be able to answer YES to all five:
- Did you read the relevant section of
docs/chp/LAW-PATTERNS.md? (or confirm no section matches) - Did you check existing laws with
bash commands/chp-law list? (or confirm no similar law exists) - Did you grep proposed patterns against existing
verify.shscripts? (or user confirmed overlap is acceptable) - Are you confident your detection method works? (or did you test it in Step 4)
- Can you explain WHY your detection method works — not just what it does?
If any answer is NO — go back. Do not write verify.sh yet.
If you cannot reach confidence after research, ask the user for guidance. Shipping a broken check is worse than shipping no check.
Now implement
After completing the Research-First Protocol, read the law's generated verify.sh template:
cat docs/chp/laws/<law-name>/verify.sh
Then edit it. Use the structure from the closest existing law (Step 2) and the patterns from LAW-PATTERNS.md (Step 1). You should have already confirmed no overlap in Step 3. Do not write from a blank page — always start from a template or existing law.
Writing the Guidance
Edit the .md file to provide context:
# Law: No API Keys
**Severity:** Error
**Action:** Blocks commits and pushes
## What this means
Never commit API keys, tokens, or secrets to this repository.
## How to comply
- Use environment variables
- Use `.env` files (already gitignored)
- Use secret management services
## Detection
Scans for patterns: `sk_`, `AIza`, `AKIA`, `Bearer eyJ`
Scope Control Examples
Apply only to TypeScript files:
"include": ["**/*.ts"]
Apply to all files except test files:
"exclude": ["**/*.test.ts", "**/*.spec.ts", "**/test/**"]
Apply to source files only (not build artifacts):
"include": ["src/**/*"],
"exclude": ["dist/**", "build/**", "**/*.min.js"]
Apply to specific directories:
"include": ["lib/**/*", "components/**/*"]
Testing Your Law
Before the law is active, you MUST test it:
# Test the verification script directly with sample input
echo "test content with VIOLATION_PATTERN" | bash docs/chp/laws/<law-name>/verify.sh
# Expected: exit 1 (violation detected)
echo "clean content without violations" | bash docs/chp/laws/<law-name>/verify.sh
# Expected: exit 0 (passes)
# Then test via CHP
chp-law test <law-name>
Both must pass. If the direct test fails, the detection logic is wrong — go back to research. Do NOT proceed with a law that can't detect its own violation pattern.
Refining Existing Laws
When a law has false positives, needs new patterns, or requires other adjustments:
Reduce False Positives
A law flags things that shouldn't be violations. Example: no-console-log flags console.error which you need.
Edit verify.sh to exclude the pattern:
# Before: grep -q 'console\.log'
# After: grep -q 'console\.log' | grep -v 'console\.error'
Test: ./commands/chp-law test no-console-log
Change Severity
Edit law.json:
// Before: "severity": "error"
// After: "severity": "warn"
Test: ./commands/chp-law test <law-name>
Add New Violation Patterns
Edit verify.sh to add patterns:
# Add Bearer token detection to no-api-keys
patterns+=("Bearer [A-Za-z0-9\\-._~+/]+=*")
Test: ./commands/chp-law test <law-name>
Adjust Hooks
A law runs at the wrong time. Example: test-coverage should run on pre-push, not pre-commit.
Edit law.json:
// Before: "hooks": ["pre-commit"]
// After: "hooks": ["pre-push"]
Then reinstall hooks:
./commands/chp-hooks disable pre-commit
./commands/chp-hooks enable pre-push
Update Guidance
When documentation doesn't match behavior, edit guidance.md to keep it accurate.
Reset Failure Count
When past issues are resolved and you want a clean slate:
./commands/chp-law reset <law-name>
Disable vs Delete
Disable temporarily: ./commands/chp-law disable <law-name> — law stays, just stops enforcing. Re-enable with ./commands/chp-law enable <law-name>.
Delete permanently: ./commands/chp-law delete <law-name> — removes the law directory and unregisters it from all hooks.
Before Refining
- Understand why the law exists — read the full guidance
- Consider the impact — changes affect everyone
- Document the change in
guidance.mdwith rationale - Always test after refining:
./commands/chp-law test <law-name>
Available Commands
chp-law create <name> [--hooks=<list>] # Create new law
chp-law list # List all laws
chp-law delete <name> # Delete a law
chp-law test <name> # Test verification
chp-law reset <name> # Reset failure count
chp-law enable <name> # Enable a disabled law
chp-law disable <name> # Disable without deleting
chp-status # Show system status
Hook Types
CHP supports 25+ hook types across Git, AI/Agent, and CI/CD operations:
Git Hooks (15): pre-commit, post-commit, pre-push, post-merge, commit-msg, prepare-commit-msg, pre-rebase, post-checkout, post-rewrite, applypatch-msg, pre-applypatch, post-applypatch, update, pre-auto-gc, post-update
AI/Agent Hooks (6): pre-prompt, post-prompt, pre-tool, post-tool, pre-response, post-response
CI/CD Hooks (4): pre-build, post-build, pre-deploy, post-deploy
Use chp-hooks detect to see available hooks and chp-hooks list to see installed hooks.
Auto-Tightening
When a law's verification fails:
- The operation is blocked
- Failure count increments
- Guidance is automatically strengthened with violation history
- Future attempts get stricter context
Pattern Reference
Comprehensive pattern library at docs/chp/LAW-PATTERNS.md.
When users describe what they want to enforce, match their language to patterns and ask "you mean something like this?"
| User Request | Detection Method | Template |
|---|---|---|
| "no API keys" | Regex | sk_|AIza|AKIA |
| "no console.log" | Regex | console\.log |
| "too nested" | AST | Nesting depth check |
| "enforce style" | Delegated | ESLint/Prettier |
| "console in tests only" | Hybrid | Regex + file path |
Before writing custom verify.sh:
- Check
docs/chp/LAW-PATTERNS.mdfor existing patterns - Match user's request to a pattern
- Use the provided template
- Adapt to specific requirements
For the complete agent flow:
- See
docs/chp/AGENT-FLOW.mdfor step-by-step walkthroughs
Common Law Patterns
Security: no-api-keys, no-hardcoded-credentials, no-debug-endpoints Quality: max-file-size, max-function-length, required-documentation Style: no-console-log, import-ordering, type-annotations
Post-Write Fix
After creating or editing a law, spawn a fix agent that reads the law fresh from disk and corrects any inconsistencies between law.json, verify.sh, and guidance.md:
Use the Agent tool to spawn a background agent with this prompt:
"Run the chp:review-laws skill for the law '<law-name>'. Read all three files fresh from disk, fix all inconsistencies, commit fixes, and report what you changed."
This runs in a separate agent context with fresh eyes — no assumptions from the writing process.