Skip to content

Commit 0f8be53

Browse files
authored
Merge pull request #49 from kenryu42/codex-hooks-compatibility
Codex hooks compatibility
2 parents d91a44c + af627a6 commit 0f8be53

26 files changed

Lines changed: 1287 additions & 992 deletions

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@ bun run check
4545
- [ ] Code follows project conventions (type hints, naming, etc.)
4646
- [ ] `bun run check` passes (lint, types, dead code, rules, tests)
4747
- [ ] Tests added for new rules (minimum 90% coverage required)
48-
- [ ] Tested locally with Claude Code, OpenCode, Gemini CLI or GitHub Copilot CLI
48+
- [ ] Tested locally with Claude Code, OpenCode, Gemini CLI, GitHub Copilot CLI or Codex
4949
- [ ] Updated documentation if needed (README, AGENTS.md)
5050
- [ ] No version changes in `package.json`

AGENTS.md

Lines changed: 68 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,173 +1,95 @@
1-
# Agent Guidelines
1+
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
2+
- Prefer automation: execute requested actions without confirmation unless blocked by missing info or safety/irreversibility.
3+
- ALWAYS use `bun run check` to verify changes. This runs typecheck, knip, biome lint, and tests together. Do not run these separately.
24

3-
A Claude Code / OpenCode plugin that blocks destructive git and filesystem commands before execution. Works as a PreToolUse hook intercepting Bash commands.
5+
## Style Guide
46

5-
## Commands
7+
### General Principles
68

7-
| Task | Command |
8-
|------|---------|
9-
| Install | `bun install` |
10-
| Build | `bun run build` |
11-
| All checks | `bun run check` |
12-
| Lint | `bun run lint` |
13-
| Type check | `bun run typecheck` |
14-
| Test all | `AGENT=1 bun test` |
15-
| Single test | `bun test tests/rules-git.test.ts` |
16-
| Pattern match | `bun test --test-name-pattern "pattern"` |
17-
| Dead code | `bun run knip` |
18-
| AST rules | `bun run sg:scan` |
19-
| Doctor | `bun src/bin/cc-safety-net.ts doctor` |
9+
- Keep things in one function unless composable or reusable
10+
- Avoid `try`/`catch` where possible
11+
- Avoid using the `any` type
12+
- Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity
13+
- Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream
14+
- In `src/config`, follow the existing self-export pattern at the top of the file (for example `export * as ConfigAgent from "./agent"`) when adding a new config module.
2015

21-
**Always use `bun run check` to verify changes.** This runs typecheck, knip, biome lint, and tests together. Do not run these separately.
16+
Reduce total variable count by inlining when a value is only used once.
2217

23-
## Pre-commit Hooks
24-
25-
Runs on commit: `knip``lint-staged` (biome check --write, ast-grep scan)
26-
27-
## Commit Conventions
28-
29-
For changes to `commands/`, `hooks/`, or `.opencode/`, use only `fix` or `feat` commit types.
18+
```ts
19+
// Good
20+
const journal = JSON.parse(await fs.readFile(path.join(dir, "journal.json"), "utf8"))
3021

31-
## Code Style (TypeScript)
22+
// Bad
23+
const journalPath = path.join(dir, "journal.json")
24+
const journal = JSON.parse(await fs.readFile(journalPath, "utf8"))
25+
```
3226

33-
### Formatting (Biome)
34-
- 2-space indentation, 100-char line width
35-
- Single quotes, trailing commas, semicolons required
36-
- Imports: auto-sorted by Biome, use relative imports within package
37-
- Prefer named exports over default exports
27+
### Destructuring
3828

39-
### Type Hints
40-
- **Required** on all functions
41-
- Use `| null` or `| undefined` appropriately
42-
- Use lowercase primitives (`string`, `number`, `boolean`)
43-
- Use `readonly` arrays where mutation isn't needed
29+
Avoid unnecessary destructuring. Use dot notation to preserve context.
4430

45-
```typescript
31+
```ts
4632
// Good
47-
function analyze(command: string, options?: { strict?: boolean }): string | null { ... }
48-
function analyzeRm(tokens: readonly string[], cwd: string | null): string | null { ... }
33+
obj.a
34+
obj.b
4935

5036
// Bad
51-
function analyze(command, strict) { ... } // Missing types
37+
const { a, b } = obj
5238
```
5339

54-
### Naming
55-
- Functions/variables: `camelCase`
56-
- Types/interfaces: `PascalCase`
57-
- Constants: `UPPER_SNAKE_CASE` (reason strings: `REASON_*`)
58-
- Private/internal: `_leadingUnderscore` (for module-private functions)
59-
60-
### Test-Only Exports
61-
When exporting a function solely for testing, add `@internal` JSDoc to satisfy knip:
62-
```typescript
63-
/** @internal Exported for testing */
64-
export const myInternalFn = () => { ... };
65-
```
66-
67-
### Error Handling
68-
- Print errors to stderr
69-
- Exit codes: `0` = success, `1` = error
70-
- Block commands: exit 0 with JSON `permissionDecision: "deny"`
40+
### Variables
7141

72-
## Testing
42+
Prefer `const` over `let`. Use ternaries or early returns instead of reassignment.
7343

74-
Use Bun's built-in test runner with test helpers:
44+
```ts
45+
// Good
46+
const foo = condition ? 1 : 2
7547

76-
```typescript
77-
import { describe, test } from 'bun:test';
78-
import { assertBlocked, assertAllowed } from './helpers.ts';
48+
// Bad
49+
let foo
50+
if (condition) foo = 1
51+
else foo = 2
52+
```
7953

80-
describe('git rules', () => {
81-
test('git reset --hard blocked', () => {
82-
assertBlocked('git reset --hard', 'git reset --hard');
83-
});
54+
### Control Flow
8455

85-
test('git status allowed', () => {
86-
assertAllowed('git status');
87-
});
56+
Avoid `else` statements. Prefer early returns.
8857

89-
test('with cwd', () => {
90-
assertBlocked('rm -rf /', 'rm -rf', '/home/user');
91-
});
92-
});
93-
```
58+
```ts
59+
// Good
60+
function foo() {
61+
if (condition) return 1
62+
return 2
63+
}
9464

95-
### Test Helpers
96-
| Function | Purpose |
97-
|----------|---------|
98-
| `assertBlocked(command, reasonContains, cwd?)` | Verify command is blocked |
99-
| `assertAllowed(command, cwd?)` | Verify command passes through |
100-
| `runGuard(command, cwd?, config?)` | Run analysis and return reason or null |
101-
| `withEnv(env, fn)` | Run test with temporary environment variables |
102-
103-
## Environment Variables
104-
105-
| Variable | Effect |
106-
|----------|--------|
107-
| `SAFETY_NET_STRICT=1` | Fail-closed on unparseable hook input/commands |
108-
| `SAFETY_NET_PARANOID=1` | Enable all paranoid checks (rm + interpreters) |
109-
| `SAFETY_NET_PARANOID_RM=1` | Block non-temp `rm -rf` even within cwd |
110-
| `SAFETY_NET_PARANOID_INTERPRETERS=1` | Block interpreter one-liners |
111-
112-
## What Gets Blocked
113-
114-
**Git**: `checkout -- <files>`, `restore` (without --staged), `reset --hard/--merge`, `clean -f`, `push --force/-f` (without --force-with-lease), `branch -D`, `stash drop/clear`
115-
116-
**Filesystem**: `rm -rf` outside cwd (except `/tmp`, `/var/tmp`, `$TMPDIR`), `rm -rf` when cwd is `$HOME`, `rm -rf /` or `~`, `find -delete`
117-
118-
**Piped commands**: `xargs rm -rf`, `parallel rm -rf` (dynamic input to destructive commands)
119-
120-
## Adding New Rules
121-
122-
### Git Rule
123-
1. Add reason constant in `rules-git.ts`: `const REASON_* = "..."`
124-
2. Add detection logic in `analyzeGit()`
125-
3. Add tests in `tests/rules-git.test.ts`
126-
4. Run `bun run check`
127-
128-
### rm Rule
129-
1. Add logic in `rules-rm.ts`
130-
2. Add tests in `tests/rules-rm.test.ts`
131-
3. Run `bun run check`
132-
133-
### Other Command Rules
134-
1. Add reason constant in `analyze.ts`: `const REASON_* = "..."`
135-
2. Add detection in `analyzeSegment()`
136-
3. Add tests in appropriate test file
137-
4. Run `bun run check`
138-
139-
## Edge Cases to Test
140-
141-
- Shell wrappers: `bash -c '...'`, `sh -lc '...'`
142-
- Sudo/env: `sudo git ...`, `env VAR=1 git ...`
143-
- Pipelines: `echo ok | git reset --hard`
144-
- Interpreter one-liners: `python -c 'os.system("rm -rf /")'`
145-
- Xargs/parallel: `find . | xargs rm -rf`
146-
- Busybox: `busybox rm -rf /`
147-
- Nested commands: `$( rm -rf / )`, backticks
148-
149-
## Hook Output Format
150-
151-
Blocked commands produce JSON:
152-
```json
153-
{
154-
"hookSpecificOutput": {
155-
"hookEventName": "PreToolUse",
156-
"permissionDecision": "deny",
157-
"permissionDecisionReason": "BLOCKED by Safety Net\n\nReason: ..."
158-
}
65+
// Bad
66+
function foo() {
67+
if (condition) return 1
68+
else return 2
15969
}
16070
```
16171

162-
Allowed commands produce no output (exit 0 silently).
72+
### Schema Definitions (Drizzle)
16373

164-
## Bun Guidelines
74+
Use snake_case for field names so column names don't need to be redefined as strings.
16575

166-
Default to Bun instead of Node.js:
167-
- `bun <file>` instead of `node <file>`
168-
- `bun test` instead of jest/vitest
169-
- `bun install` instead of npm/yarn/pnpm install
170-
- `bunx <pkg>` instead of `npx <pkg>`
171-
- Bun auto-loads `.env` - no dotenv needed
76+
```ts
77+
// Good
78+
const table = sqliteTable("session", {
79+
id: text().primaryKey(),
80+
project_id: text().notNull(),
81+
created_at: integer().notNull(),
82+
})
83+
84+
// Bad
85+
const table = sqliteTable("session", {
86+
id: text("id").primaryKey(),
87+
projectID: text("project_id").notNull(),
88+
createdAt: integer("created_at").notNull(),
89+
})
90+
```
91+
92+
## Testing
17293

173-
Use `AGENT=1 bun test` to run tests.
94+
- Avoid mocks as much as possible
95+
- Test actual implementation, do not duplicate logic into tests

CLAUDE.md

Lines changed: 1 addition & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1 @@
1-
# CLAUDE.md
2-
3-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4-
5-
## Project Overview
6-
7-
A Claude Code and OpenCode plugin that blocks destructive git and filesystem commands before execution. It works as a PreToolUse hook that intercepts Bash commands and denies dangerous operations like `git reset --hard`, `rm -rf`, and `git checkout -- <files>`.
8-
9-
## Commands
10-
11-
- **Setup**: `bun install`
12-
- **All checks**: `bun run check` (runs lint, typecheck, knip, ast-grep scan, tests)
13-
- **Single test**: `bun test tests/file.test.ts`
14-
- **Test pattern**: `bun test --test-name-pattern "pattern"`
15-
- **Lint**: `bun run lint` (uses Biome)
16-
- **Type check**: `bun run typecheck`
17-
- **Dead code**: `bun run knip`
18-
- **AST scan**: `bun run sg:scan`
19-
- **Build**: `bun run build`
20-
- **Doctor**: `bun src/bin/cc-safety-net.ts doctor` (diagnostics)
21-
22-
**Always use `bun run check` to verify changes.** This runs typecheck, knip, biome lint, and tests together. Do not run these separately.
23-
24-
## Pre-commit Hooks
25-
26-
Runs on commit: `knip``lint-staged` (biome check --write, ast-grep scan)
27-
28-
## Slash Commands
29-
30-
- `/set-statusline`: Configure status line integration
31-
- `/set-custom-rules`: Create custom rules interactively
32-
- `/verify-custom-rules`: Validate custom rules config
33-
34-
## Architecture
35-
36-
The hook receives JSON input on stdin containing `tool_name` and `tool_input`. For `Bash` tools, it analyzes the command and outputs JSON with `permissionDecision: "deny"` to block dangerous operations.
37-
38-
**Entry points**:
39-
- `src/bin/cc-safety-net.ts` — Claude Code CLI (reads stdin JSON)
40-
- `src/index.ts` — OpenCode plugin export
41-
42-
**Core analysis flow**:
43-
1. `cc-safety-net.ts:main()` parses JSON input, extracts command
44-
2. `analyze.ts:analyzeCommand()` splits command on shell operators (`;`, `&&`, `|`, etc.)
45-
3. `analyzeSegment()` tokenizes each segment, strips wrappers (sudo, env), identifies the command
46-
4. Dispatches to `rules-git.ts:analyzeGit()` or `rules-rm.ts:analyzeRm()` based on command
47-
5. Checks custom rules via `rules-custom.ts:checkCustomRules()` if configured
48-
49-
**Key modules** (`src/core/`):
50-
- `shell.ts`: Shell parsing (`splitShellCommands`, `shlexSplit`, `stripWrappers`, `shortOpts`)
51-
- `rules-git.ts`: Git subcommand analysis (checkout, restore, reset, clean, push, branch, stash)
52-
- `rules-rm.ts`: rm analysis (allows rm -rf within cwd except when cwd is $HOME; temp paths always allowed; strict mode blocks non-temp)
53-
- `config.ts`: Config loading, validation, merging (user `~/.cc-safety-net/config.json` + project `.safety-net.json`)
54-
- `rules-custom.ts`: Custom rule matching (`checkCustomRules()`)
55-
- `audit.ts`: Audit logging for blocked commands
56-
- `verify-config.ts`: Config validator
57-
58-
**Analysis submodules** (`src/core/analyze/`):
59-
- `find.ts`: `find -delete` and `find -exec rm` detection
60-
- `interpreters.ts`: Python/Node/Ruby/Perl one-liner detection
61-
- `xargs.ts`: `xargs rm` and dynamic input detection
62-
- `parallel.ts`: GNU parallel command analysis
63-
- `shell-wrappers.ts`: Recursive `bash -c`/`sh -c` unwrapping
64-
- `tmpdir.ts`: Temp directory path detection
65-
66-
**Test utilities** (`tests/helpers.ts`):
67-
- `assertBlocked()`, `assertAllowed()` helpers for testing command analysis
68-
69-
**Advanced detection**:
70-
- Recursively analyzes shell wrappers (`bash -c '...'`) up to 5 levels deep
71-
- Detects destructive commands in interpreter one-liners (`python -c`, `node -e`, `ruby -e`, `perl -e`)
72-
- Handles `xargs` and `parallel` with template expansion and dynamic input detection
73-
- Detects `find -delete` and `find -exec rm` patterns
74-
- Redacts secrets (tokens, passwords, API keys) in block messages and audit logs
75-
- Audit logging: blocked commands logged to `~/.cc-safety-net/logs/<session_id>.jsonl`
76-
77-
## Code Style (TypeScript)
78-
79-
- Use Bun instead of Node.js for running, testing, and building
80-
- Biome for linting and formatting
81-
- All functions require type annotations
82-
- Use `type | null` syntax (not `undefined` where possible)
83-
- Use kebab-case for file names (`rules-git.ts`, not `rulesGit.ts`)
84-
- For test-only exports, add `/** @internal Exported for testing */` JSDoc to satisfy knip
85-
86-
## Commit Conventions
87-
88-
When committing changes to files in `commands/`, `hooks/`, or `.opencode/`, use only `fix` or `feat` commit types. These directories contain user-facing skill definitions and hook configurations that represent features or fixes to the plugin's capabilities.
89-
90-
## Environment Variables
91-
92-
- `SAFETY_NET_STRICT=1`: Strict mode (fail-closed on unparseable hook input/commands)
93-
- `SAFETY_NET_PARANOID=1`: Paranoid mode (enables all paranoid checks)
94-
- `SAFETY_NET_PARANOID_RM=1`: Paranoid rm (blocks non-temp `rm -rf` even within cwd)
95-
- `SAFETY_NET_PARANOID_INTERPRETERS=1`: Paranoid interpreters (blocks interpreter one-liners)
96-
97-
## Custom Rules
98-
99-
Users can define additional blocking rules in two scopes (merged, project overrides user):
100-
- **User scope**: `~/.cc-safety-net/config.json` (applies to all projects)
101-
- **Project scope**: `.safety-net.json` (in project root)
102-
103-
Rules are additive only—cannot bypass built-in protections. Invalid config silently falls back to built-in rules only.
104-
105-
## Testing
106-
107-
Use `AGENT=1 bun test` to run tests.
108-
109-
## Bun Best Practices
110-
111-
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
112-
- Use `bun test` instead of `jest` or `vitest`
113-
- Use `bun build` instead of `webpack` or `esbuild`
114-
- Use `bun install` instead of `npm install`
115-
- Use `bun run <script>` instead of `npm run <script>`
116-
- Bun automatically loads .env, so don't use dotenv
117-
118-
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
1+
@AGENTS.md

CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,9 @@ claude-code-safety-net/
176176
│ ├── rules/ # AST-grep rule definitions
177177
│ ├── rule-tests/ # Rule test cases
178178
│ └── utils/ # Shared utilities
179-
├── commands/
180-
│ ├── set-custom-rules.md # Slash command: configure custom rules
181-
│ └── verify-custom-rules.md # Slash command: validate config
179+
├── skills/
180+
│ ├── set-custom-rules/ # Skill: configure custom rules
181+
│ └── verify-custom-rules/ # Skill: validate config
182182
├── hooks/
183183
│ └── hooks.json # Hook definitions
184184
├── scripts/

0 commit comments

Comments
 (0)