# Contributing to Claude Code Safety Net

First off, thanks for taking the time to contribute! This document provides guidelines and instructions for contributing to cc-safety-net.

## Table of Contents

- [Code of Conduct](#code-of-conduct)
- [Before You Start: Proposing New Features](#before-you-start-proposing-new-features)
- [Getting Started](#getting-started)
  - [Prerequisites](#prerequisites)
  - [Development Setup](#development-setup)
  - [Testing Your Changes Locally](#testing-your-changes-locally)
- [Project Structure](#project-structure)
- [Development Workflow](#development-workflow)
  - [Build Commands](#build-commands)
  - [Code Style & Conventions](#code-style--conventions)
- [Making Changes](#making-changes)
  - [Adding a Git Rule](#adding-a-git-rule)
  - [Adding an rm Rule](#adding-an-rm-rule)
  - [Adding Other Command Rules](#adding-other-command-rules)
- [Pull Request Process](#pull-request-process)
- [Publishing](#publishing)
- [Getting Help](#getting-help)

## Code of Conduct

Be respectful, inclusive, and constructive. We're all here to make better tools together.

## Before You Start: Proposing New Features

**Please open an issue to discuss new features before implementing them.**

This project has a focused scope: **preventing coding agents from making accidental mistakes that cause data loss** (e.g., `rm -rf ~/`, `git reset --hard`). It is NOT a general security hardening tool or an attack prevention system.

### Why Discuss First?

1. **Scope alignment** — Your idea might be great but outside the project's scope
2. **Approach feedback** — We can suggest the best way to implement it
3. **Avoid wasted effort** — Save time for both you and maintainers

### When to Open an Issue First

| Scenario | Open Issue First? |
|----------|-------------------|
| New detection rule (git, rm, etc.) | **Yes** |
| New command category to block | **Yes** |
| Architectural changes | **Yes** |
| New configuration options | **Yes** |
| Typo/documentation fixes | No, just PR |
| Small bug fixes with obvious solution | No, just PR |

### What to Include in Your Proposal

- **What** you want to add/change
- **Why** it fits the project scope (preventing accidental data loss)
- **Real-world scenario** where this would help
- Any **trade-offs** you've considered

A quick 5-minute issue can save hours of implementation time on both sides.

## Getting Started

### Prerequisites

- **Bun** - Required runtime and package manager ([install guide](https://bun.sh/docs/installation))
- **Claude Code** or **OpenCode** - For testing the plugin

### Development Setup

```bash
# Clone the repository
git clone https://github.com/kenryu42/claude-code-safety-net.git
cd claude-code-safety-net

# Install dependencies
bun install

# Build for distribution
bun run build

# Check for all lint errors, type errors, dead code and run tests
bun run check
```

### Testing Your Changes Locally

## Claude Code

1. **Build the project**:
   ```bash
   bun run build
   ```

2. **Disable the safety-net plugin** in Claude Code (if installed) and exit Claude Code completely.

3. **Run Claude Code with the local plugin**:
   ```bash
   claude --plugin-dir .
   ```

4. **Test blocked commands** to verify your changes:
   ```bash
   # This should be blocked
   git checkout -- README.md

   # This should be allowed
   git checkout -b test-branch
   ```

> [!NOTE]
> See the [official documentation](https://docs.anthropic.com/en/docs/claude-code/plugins#test-your-plugins-locally) for more details on testing plugins locally.

## OpenCode

1. **Build the project**:
   ```bash
   bun run build
   ```

2. **Update your OpenCode config** (`~/.config/opencode/opencode.json` or `opencode.jsonc`):
   ```json
   {
     "plugin": [
       "file:///absolute/path/to/cc-safety-net/dist/index.js"
     ]
   }
   ```
   
   For example, if your project is at `/Users/yourname/projects/cc-safety-net`:
   ```json
   {
     "plugin": [
       "file:///Users/yourname/projects/cc-safety-net/dist/index.js"
     ]
   }
   ```

> [!NOTE]
> Remove `"cc-safety-net"` from the plugin array if it exists, to avoid conflicts with the npm version.
> Or comment out the line if you're using `opencode.jsonc`.

3. **Restart OpenCode** to load the changes.

4. **Verify the plugin is loaded:** Run `/status` and confirm that the plugin name appears as `dist`.

5. **Test blocked commands** to verify your changes:
   ```bash
   # This should be blocked
   git checkout -- README.md

   # This should be allowed
   git checkout -b test-branch
   ```

> [!NOTE]
> See the [official documentation](https://opencode.ai/docs/plugins/) for more details on OpenCode plugins.

## Project Structure

```
claude-code-safety-net/
├── .claude-plugin/
│   ├── plugin.json           # Plugin metadata
│   └── marketplace.json      # Marketplace config
├── .github/
│   ├── workflows/            # CI/CD workflows
│   │   ├── ci.yml
│   │   ├── lint-github-actions-workflows.yml
│   │   └── publish.yml
│   └── pull_request_template.md
├── .husky/
│   └── pre-commit            # Pre-commit hook (knip + lint-staged)
├── assets/
│   └── cc-safety-net.schema.json  # JSON schema for config validation
├── ast-grep/
│   ├── rules/                # AST-grep rule definitions
│   ├── rule-tests/           # Rule test cases
│   └── utils/                # Shared utilities
├── commands/
│   ├── set-custom-rules.md   # Slash command: configure custom rules
│   └── verify-custom-rules.md # Slash command: validate config
├── hooks/
│   └── hooks.json            # Hook definitions
├── scripts/
│   ├── build-schema.ts       # Generate JSON schema
│   ├── generate-changelog.ts # Changelog generation
│   └── publish.ts            # Release automation
├── src/
│   ├── index.ts              # OpenCode plugin export
│   ├── types.ts              # Shared type definitions
│   ├── bin/
│   │   └── cc-safety-net.ts  # Claude Code CLI entry point
│   └── core/
│       ├── analyze.ts        # Main analysis orchestration
│       ├── analyze/          # Analysis submodules
│       │   ├── analyze-command.ts  # Command analysis entry
│       │   ├── constants.ts        # Shared constants
│       │   ├── dangerous-text.ts   # Text pattern detection
│       │   ├── find.ts             # find command analysis
│       │   ├── interpreters.ts     # Interpreter one-liner detection
│       │   ├── parallel.ts         # parallel command analysis
│       │   ├── rm-flags.ts         # rm flag parsing
│       │   ├── segment.ts          # Command segment analysis
│       │   ├── shell-wrappers.ts   # Shell wrapper detection
│       │   ├── tmpdir.ts           # Temp directory detection
│       │   └── xargs.ts            # xargs command analysis
│       ├── audit.ts          # Audit logging
│       ├── config.ts         # Config loading
│       ├── custom-rules-doc.ts # Custom rules documentation
│       ├── env.ts            # Environment variable utilities
│       ├── format.ts         # Output formatting
│       ├── rules-git.ts      # Git subcommand analysis
│       ├── rules-rm.ts       # rm command analysis
│       ├── rules-custom.ts   # Custom rule evaluation
│       ├── shell.ts          # Shell parsing utilities
│       └── verify-config.ts  # Config validator
├── tests/
│   ├── helpers.ts            # Test utilities
│   ├── analyze-coverage.test.ts
│   ├── audit.test.ts
│   ├── config.test.ts
│   ├── custom-rules.test.ts
│   ├── custom-rules-integration.test.ts
│   ├── edge-cases.test.ts
│   ├── find.test.ts
│   ├── parsing-helpers.test.ts
│   ├── rules-git.test.ts
│   ├── rules-rm.test.ts
│   └── verify-config.test.ts
├── .lintstagedrc.json        # Lint-staged config (biome + ast-grep)
├── biome.json                # Linter/formatter config
├── knip.ts                   # Dead code detection config
├── package.json              # Project config
├── sgconfig.yml              # AST-grep config
├── tsconfig.json             # TypeScript config
├── tsconfig.typecheck.json   # Type-check only config
├── AGENTS.md                 # AI agent guidelines
├── CLAUDE.md                 # Claude Code context
└── README.md                 # Project documentation
```

| Module | Purpose |
|--------|---------|
| `analyze.ts` | Main entry, command analysis orchestration |
| `analyze/` | Submodules for specific analysis tasks (find, xargs, parallel, interpreters, etc.) |
| `audit.ts` | Audit logging to `~/.cc-safety-net/logs/` |
| `config.ts` | Config loading (`.safety-net.json`, `~/.cc-safety-net/config.json`) |
| `env.ts` | Environment variable utilities (`envTruthy`) |
| `format.ts` | Output formatting (`formatBlockedMessage`) |
| `rules-git.ts` | Git rules (checkout, restore, reset, clean, push, branch, stash) |
| `rules-rm.ts` | rm analysis (cwd-relative, temp paths, root/home detection) |
| `rules-custom.ts` | Custom rule matching |
| `shell.ts` | Shell parsing (`splitShellCommands`, `shlexSplit`, `stripWrappers`) |
| `verify-config.ts` | Config file validation |

## Development Workflow

### Build Commands

```bash
# Run all checks (lint, type check, dead code, ast-grep scan, tests)
bun run check

# Individual commands
bun run lint          # Lint + format (Biome)
bun run typecheck     # Type check
bun run knip          # Dead code detection
bun run sg:scan       # AST pattern scan
bun test              # Run tests

# Run specific test
bun test tests/rules-git.test.ts

# Run tests matching pattern
bun test --test-name-pattern "checkout"

# Build for distribution
bun run build
```

### Code Style & Conventions

| Convention | Rule |
|------------|------|
| Runtime | **Bun** |
| Package Manager | **bun only** (`bun install`, `bun run`) |
| Formatter/Linter | **Biome** |
| Type Hints | Required on all functions |
| Type Syntax | `type \| null` preferred over `type \| undefined` |
| File Naming | `kebab-case` (e.g., `rules-git.ts`, not `rulesGit.ts`) |
| Function Naming | `camelCase` for functions, `PascalCase` for types/interfaces |
| Constants | `SCREAMING_SNAKE_CASE` for reason constants |
| Imports | Relative imports within package |

**Examples**:

```typescript
// Good
export function analyzeCommand(
  command: string,
  options?: { strict?: boolean }
): string | null {
  // ...
}

// Bad
export function analyzeCommand(command, options) {  // Missing type hints
  // ...
}
```

**Anti-Patterns (Do Not Do)**:
- Using npm/yarn/pnpm instead of bun
- Suppressing type errors with `@ts-ignore` or `any`
- Skipping tests for new rules
- Modifying version in `package.json` directly

## Making Changes

### Adding a Git Rule

1. **Add reason constant** in `src/core/rules-git.ts`:
   ```typescript
   const REASON_MY_RULE = "git my-command does something dangerous. Use safer alternative.";
   ```

2. **Add detection logic** in `analyzeGit()`:
   ```typescript
   if (subcommand === "my-command" && tokens.includes("--dangerous-flag")) {
     return REASON_MY_RULE;
   }
   ```

3. **Add tests** in `tests/rules-git.test.ts`:
   ```typescript
   describe("git my-command", () => {
     test("dangerous flag blocked", () => {
       assertBlocked("git my-command --dangerous-flag", "dangerous");
     });

     test("safe flag allowed", () => {
       assertAllowed("git my-command --safe-flag");
     });
   });
   ```

4. **Run checks**:
   ```bash
   bun run check
   ```

### Adding an rm Rule

1. **Add logic** in `src/core/rules-rm.ts`
2. **Add tests** in `tests/rules-rm.test.ts`
3. **Run checks**: `bun run check`

### Adding Other Command Rules

1. **Add reason constant** in `src/core/analyze.ts`:
   ```typescript
   const REASON_MY_COMMAND = "my-command is dangerous because...";
   ```

2. **Add detection** in `analyzeSegment()`

3. **Add tests** in the appropriate test file

4. **Run checks**: `bun run check`

### Edge Cases to Test

When adding rules, ensure you test these edge cases:

- Shell wrappers: `bash -c '...'`, `sh -lc '...'`
- Sudo/env prefixes: `sudo git ...`, `env VAR=1 git ...`
- Pipelines: `echo ok | git reset --hard`
- Interpreter one-liners: `python -c 'os.system("...")'`
- Xargs/parallel: `find . | xargs rm -rf`
- Busybox: `busybox rm -rf /`

## Pull Request Process

1. **Fork** the repository and create your branch from `main`
2. **Make changes** following the conventions above
3. **Run all checks** locally:
   ```bash
   bun run check  # Must pass with no errors
   ```
4. **Test in Claude Code and OpenCode** using the local plugin method described above
5. **Commit** with clear, descriptive messages:
   - Use present tense ("Add rule" not "Added rule")
   - Reference issues if applicable ("Fix #123")
6. **Push** to your fork and create a Pull Request
7. **Describe** your changes clearly in the PR description

### PR Checklist

- [ ] Code follows project conventions (type hints, naming, etc.)
- [ ] `bun run check` passes (lint, types, dead code, tests)
- [ ] Tests added for new rules
- [ ] Tested locally with Claude Code and Opencode
- [ ] Updated documentation if needed (README, AGENTS.md)
- [ ] No version changes in `package.json`

## Publishing

**Important**: Version bumping and releases are handled by maintainers only.

- **Never** modify the version in `package.json` or `plugin.json` directly
- Maintainers handle versioning, tagging, and releases

## Getting Help

- **Project Knowledge**: Check `CLAUDE.md` or `AGENTS.md` for detailed architecture and conventions
- **Code Patterns**: Review existing implementations in `src/core/`
- **Test Patterns**: See `tests/helpers.ts` for test utilities
- **Issues**: Open an issue for bugs or feature requests

---

Thank you for contributing to Claude Code Safety Net! Your efforts help keep AI-assisted coding safer for everyone.
