Reorganize: Move all skills to skills/ folder
- Created skills/ directory - Moved 272 skills to skills/ subfolder - Kept agents/ at root level - Kept installation scripts and docs at root level Repository structure: - skills/ - All 272 skills from skills.sh - agents/ - Agent definitions - *.sh, *.ps1 - Installation scripts - README.md, etc. - Documentation Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
220
skills/plugins/claude-code-safety-net/AGENTS.md
Normal file
220
skills/plugins/claude-code-safety-net/AGENTS.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# Agent Guidelines
|
||||
|
||||
A Claude Code / OpenCode plugin that blocks destructive git and filesystem commands before execution. Works as a PreToolUse hook intercepting Bash commands.
|
||||
|
||||
## Commands
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| Install | `bun install` |
|
||||
| Build | `bun run build` |
|
||||
| All checks | `bun run check` |
|
||||
| Lint | `bun run lint` |
|
||||
| Type check | `bun run typecheck` |
|
||||
| Test all | `AGENT=1 bun test` |
|
||||
| Single test | `bun test tests/rules-git.test.ts` |
|
||||
| Pattern match | `bun test --test-name-pattern "pattern"` |
|
||||
| Dead code | `bun run knip` |
|
||||
| AST rules | `bun run sg:scan` |
|
||||
|
||||
**`bun run check`** runs: biome check → typecheck → knip → ast-grep scan → bun test
|
||||
|
||||
## Pre-commit Hooks
|
||||
|
||||
Runs on commit (in order): knip → lint-staged (biome check --write)
|
||||
|
||||
## Commit Conventions
|
||||
|
||||
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.
|
||||
|
||||
## Code Style (TypeScript)
|
||||
|
||||
### Formatting
|
||||
- Formatter: Biome
|
||||
- Line length: configured in `biome.json`
|
||||
- Use tabs for indentation (Biome default)
|
||||
|
||||
### Type Hints
|
||||
- **Required** on all functions
|
||||
- Use `| null` or `| undefined` appropriately
|
||||
- Use lowercase primitive types (`string`, `number`, `boolean`)
|
||||
- Use `readonly` arrays where mutation isn't needed
|
||||
|
||||
```typescript
|
||||
// Good
|
||||
function analyze(command: string, options?: { strict?: boolean }): string | null { ... }
|
||||
function analyzeRm(tokens: readonly string[], cwd: string | null): string | null { ... }
|
||||
|
||||
// Bad
|
||||
function analyze(command, strict) { ... } // Missing types
|
||||
```
|
||||
|
||||
### Imports
|
||||
- Order: handled by Biome (sorted automatically)
|
||||
- Use relative imports within same package
|
||||
- Prefer named exports over default exports
|
||||
|
||||
```typescript
|
||||
import { parse } from "shell-quote"
|
||||
import type { Config, HookInput } from "../types"
|
||||
import { analyzeGit } from "./rules-git"
|
||||
import { splitShellCommands } from "./shell"
|
||||
```
|
||||
|
||||
### Naming
|
||||
- Functions/variables: `camelCase`
|
||||
- Types/interfaces: `PascalCase`
|
||||
- Constants: `UPPER_SNAKE_CASE` (reason strings: `REASON_*`)
|
||||
- Private/internal: `_leadingUnderscore` (for module-private functions)
|
||||
|
||||
### Error Handling
|
||||
- Print errors to stderr
|
||||
- Return exit codes: `0` = success, `1` = error
|
||||
- Block commands: exit 0 with JSON `permissionDecision: "deny"`
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
src/
|
||||
├── index.ts # OpenCode plugin export (main entry)
|
||||
├── types.ts # Shared types and constants
|
||||
├── bin/
|
||||
│ └── cc-safety-net.ts # Claude Code CLI wrapper
|
||||
└── core/
|
||||
├── analyze.ts # Main analysis logic
|
||||
├── config.ts # Config loading (.safety-net.json)
|
||||
├── shell.ts # Shell parsing (uses shell-quote)
|
||||
├── rules-git.ts # Git subcommand analysis
|
||||
├── rules-rm.ts # rm command analysis
|
||||
└── rules-custom.ts # Custom rule evaluation
|
||||
```
|
||||
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| `index.ts` | OpenCode plugin export |
|
||||
| `bin/cc-safety-net.ts` | Claude Code CLI wrapper, JSON I/O |
|
||||
| `analyze.ts` | Main entry, command analysis orchestration |
|
||||
| `config.ts` | Config loading (`.safety-net.json`), Config type |
|
||||
| `rules-custom.ts` | Custom rule evaluation (`checkCustomRules`) |
|
||||
| `rules-git.ts` | Git rules (checkout, restore, reset, clean, push, branch, stash) |
|
||||
| `rules-rm.ts` | rm analysis (cwd-relative, temp paths, root/home detection) |
|
||||
| `shell.ts` | Shell parsing (`splitShellCommands`, `shlexSplit`, `stripWrappers`) |
|
||||
|
||||
## Testing
|
||||
|
||||
Use Bun's built-in test runner with test helpers:
|
||||
|
||||
```typescript
|
||||
import { describe, test } from "bun:test"
|
||||
import { assertBlocked, assertAllowed } from "./helpers"
|
||||
|
||||
describe("git rules", () => {
|
||||
test("git reset --hard blocked", () => {
|
||||
assertBlocked("git reset --hard", "git reset --hard")
|
||||
})
|
||||
|
||||
test("git status allowed", () => {
|
||||
assertAllowed("git status")
|
||||
})
|
||||
|
||||
test("with cwd", () => {
|
||||
assertBlocked("rm -rf /", "rm -rf", "/home/user")
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Test Helpers
|
||||
| Function | Purpose |
|
||||
|----------|---------|
|
||||
| `assertBlocked(command, reasonContains, cwd?)` | Verify command is blocked |
|
||||
| `assertAllowed(command, cwd?)` | Verify command passes through |
|
||||
| `runGuard(command, cwd?, config?)` | Run analysis and return reason or null |
|
||||
| `withEnv(env, fn)` | Run test with temporary environment variables |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Effect |
|
||||
|----------|--------|
|
||||
| `SAFETY_NET_STRICT=1` | Fail-closed on unparseable hook input/commands |
|
||||
| `SAFETY_NET_PARANOID=1` | Enable all paranoid checks (rm + interpreters) |
|
||||
| `SAFETY_NET_PARANOID_RM=1` | Block non-temp `rm -rf` even within the current working directory |
|
||||
| `SAFETY_NET_PARANOID_INTERPRETERS=1` | Block interpreter one-liners like `python -c`, `node -e`, etc. |
|
||||
|
||||
## What Gets Blocked
|
||||
|
||||
**Git**: `checkout -- <files>`, `restore` (without --staged), `reset --hard/--merge`, `clean -f`, `push --force/-f` (without --force-with-lease), `branch -D`, `stash drop/clear`
|
||||
|
||||
**Filesystem**: `rm -rf` outside cwd (except `/tmp`, `/var/tmp`, `$TMPDIR`), `rm -rf` when cwd is `$HOME`, `rm -rf /` or `~`, `find -delete`
|
||||
|
||||
**Piped commands**: `xargs rm -rf`, `parallel rm -rf` (dynamic input to destructive commands)
|
||||
|
||||
## Adding New Rules
|
||||
|
||||
### Git Rule
|
||||
1. Add reason constant in `rules-git.ts`: `const REASON_* = "..."`
|
||||
2. Add detection logic in `analyzeGit()`
|
||||
3. Add tests in `tests/rules-git.test.ts`
|
||||
4. Run `bun run check`
|
||||
|
||||
### rm Rule
|
||||
1. Add logic in `rules-rm.ts`
|
||||
2. Add tests in `tests/rules-rm.test.ts`
|
||||
3. Run `bun run check`
|
||||
|
||||
### Other Command Rules
|
||||
1. Add reason constant in `analyze.ts`: `const REASON_* = "..."`
|
||||
2. Add detection in `analyzeSegment()`
|
||||
3. Add tests in appropriate test file
|
||||
4. Run `bun run check`
|
||||
|
||||
## Edge Cases to Test
|
||||
|
||||
- Shell wrappers: `bash -c '...'`, `sh -lc '...'`
|
||||
- Sudo/env: `sudo git ...`, `env VAR=1 git ...`
|
||||
- Pipelines: `echo ok | git reset --hard`
|
||||
- Interpreter one-liners: `python -c 'os.system("rm -rf /")'`
|
||||
- Xargs/parallel: `find . | xargs rm -rf`
|
||||
- Busybox: `busybox rm -rf /`
|
||||
- Nested commands: `$( rm -rf / )`, backticks
|
||||
|
||||
## Hook Output Format
|
||||
|
||||
Blocked commands produce JSON:
|
||||
```json
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "BLOCKED by Safety Net\n\nReason: ..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Allowed commands produce no output (exit 0 silently).
|
||||
|
||||
## Bun Guidelines
|
||||
|
||||
Default to using Bun instead of Node.js.
|
||||
|
||||
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
|
||||
- Use `bun test` instead of `jest` or `vitest`
|
||||
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
|
||||
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
|
||||
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
|
||||
- Use `bunx <package> <command>` instead of `npx <package> <command>`
|
||||
- Bun automatically loads .env, so don't use dotenv.
|
||||
|
||||
## APIs
|
||||
|
||||
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
|
||||
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
|
||||
- `Bun.redis` for Redis. Don't use `ioredis`.
|
||||
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
|
||||
- `WebSocket` is built-in. Don't use `ws`.
|
||||
- Bun.$`ls` instead of execa.
|
||||
|
||||
## Testing
|
||||
|
||||
Use `AGENT=1 bun test` to run tests.
|
||||
|
||||
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
|
||||
Reference in New Issue
Block a user