Files
SuperCharged-Claude-Code-Up…/skills/plugins/claude-code-safety-net/AGENTS.md
admin b723e2bd7d 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>
2026-01-23 18:05:17 +00:00

7.3 KiB

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
// 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
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:

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:

{
  "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.