claw-code/.guardrails/docs/RULES_FROM_MD.md

16 KiB

Extracting Prevention Rules from Markdown

Version: 1.0 Last Updated: 2026-02-09 Applies To: MCP Server, Web UI, Rule Authors


Overview

This document explains how prevention rules are extracted from markdown files and made available as:

  1. MCP Tools - For AI agents to validate actions
  2. Web UI Rules - For browsing and managing rules
  3. Database Storage - For persistence and querying

Markdown Rule Format

Prevention rules are defined in markdown files using a standardized format. Each rule is a section with specific metadata fields.

Basic Rule Structure

## PREVENT-XXX: Rule Title

**Pattern:** `regex-pattern-here`
**Severity:** error|warning|info
**Category:** git|bash|docker|security|general|code|test

Description of what this rule prevents and why it matters.

### Examples

**Violations:**
```bash
# Bad example that triggers this rule
rm -rf /

Compliant:

# Good example that passes
rm -i file.txt

### Required Fields

| Field | Description | Values |
|-------|-------------|--------|
| `## PREVENT-XXX: Title` | Rule identifier and name | PREVENT-001 through PREVENT-999 |
| **Pattern** | Regex pattern to match violations | Valid Go regex |
| **Severity** | Impact level | `error`, `warning`, `info` |
| **Category** | Rule classification | `git`, `bash`, `docker`, `security`, `general`, `code`, `test` |

### Optional Fields

| Field | Description | Example |
|-------|-------------|---------|
| **Language** | Target programming language | `go`, `python`, `javascript` |
| **Fix** | Suggested remediation | `Use rm -i instead` |
| **References** | Related documentation | `See AGENT_GUARDRAILS.md` |

---

## Complete Rule Example

```markdown
## PREVENT-001: Force Push Prohibition

**Pattern:** `git\s+push\s+.*--force`
**Severity:** error
**Category:** git
**Language:** bash
**Fix:** Use git push with standard options; never force push to shared branches

Prevents force pushing to git repositories, which can overwrite commit history and cause data loss for collaborators.

Force push destroys the commit history that other developers may have based their work on, making it impossible for them to merge their changes.

### Examples

**Violations:**
```bash
git push --force origin main
git push -f origin feature-branch

Compliant:

git push origin main
git push origin feature-branch

Rationale

This rule enforces the Git Safety Rules from AGENT_GUARDRAILS.md which state that force push is never allowed as it causes irreversible data loss.


---

## Rule Storage Locations

Rules are stored in multiple locations depending on their source:

### File Locations

| Location | Purpose | Format |
|----------|---------|--------|
| `.guardrails/prevention-rules/pattern-rules.json` | Regex-based rules | JSON array |
| `.guardrails/prevention-rules/semantic-rules.json` | AST-based rules | JSON array |
| `.guardrails/prevention-rules/extracted-rules.json` | Rules extracted from markdown | JSON array |
| `docs/*.md` | Source documentation | Markdown |

### Pattern Rules Example (JSON)

```json
{
  "id": "PREVENT-001",
  "name": "Force Push Prohibition",
  "pattern": "git\\s+push\\s+.*--force",
  "severity": "error",
  "category": "git",
  "description": "Prevents force pushing to git repositories",
  "examples": {
    "violation": "git push --force origin main",
    "compliant": "git push origin main"
  }
}

Semantic Rules Example (JSON)

{
  "id": "PREVENT-101",
  "name": "Hardcoded Credentials",
  "language": "go",
  "pattern": "password|token|secret|key",
  "severity": "error",
  "category": "security",
  "ast_context": "assignment",
  "description": "Detects potential hardcoded credentials"
}

Extraction Flow: MD to MCP Tool

The following diagram shows the complete flow from markdown to MCP tool:

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  Markdown File  │────▶│  RuleParser      │────▶│  ParsedRule     │
│  (docs/*.md)    │     │  (ParseRules)    │     │  (Struct)       │
└─────────────────┘     └──────────────────┘     └─────────────────┘
                                                           │
                                                           ▼
┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  MCP Tool       │◀────│  ToolGenerator   │◀────│  PreventionRule │
│  (ValidateXxx)  │     │  (GenerateTools) │     │  (DB Model)     │
└─────────────────┘     └──────────────────┘     └─────────────────┘
         │
         ▼
┌─────────────────┐
│  AI Agent       │
│  (Validation)   │
└─────────────────┘

Step-by-Step Extraction

Step 1: Parse Markdown Rules

The RuleParser scans markdown content and extracts rule sections:

// From: mcp-server/internal/ingest/rule_parser.go

parser := ingest.NewRuleParser()
content, _ := os.ReadFile("docs/AGENT_GUARDRAILS.md")

rules, err := parser.ParseRules(string(content), "AGENT_GUARDRAILS.md")
if err != nil {
    log.Fatal(err)
}

for _, rule := range rules {
    fmt.Printf("Found rule: %s - %s\n", rule.ID, rule.Name)
}

Step 2: Convert to ParsedRule

Rules are parsed into the ParsedRule struct:

// From: mcp-server/internal/models/rule.go

type ParsedRule struct {
    ID          string   `json:"id"`
    Name        string   `json:"name"`
    Pattern     string   `json:"pattern"`
    Severity    string   `json:"severity"`
    Category    string   `json:"category"`
    Description string   `json:"description"`
    Examples    []string `json:"examples"`
    Language    string   `json:"language,omitempty"`
    Fix         string   `json:"fix,omitempty"`
    FilePath    string   `json:"file_path"`
}

Step 3: Store in Database

Parsed rules are converted to PreventionRule models and stored:

// From: mcp-server/internal/database/rules.go

rule := &models.PreventionRule{
    ID:          generateRuleID(parsed.ID),
    Code:        parsed.ID,
    Name:        parsed.Name,
    Description: parsed.Description,
    Pattern:     parsed.Pattern,
    Severity:    models.Severity(parsed.Severity),
    Category:    models.Category(parsed.Category),
    Language:    parsed.Language,
    Fix:         parsed.Fix,
    Source:      "markdown",
    Version:     1,
    Enabled:     true,
}

// Store in database
if err := db.CreateRule(ctx, rule); err != nil {
    return err
}

Step 4: Generate MCP Tools

Rules become MCP tools through the tool generator:

// From: mcp-server/internal/mcp/server.go

func (s *Server) generateRuleTools(rules []models.PreventionRule) []Tool {
    tools := make([]Tool, 0, len(rules))

    for _, rule := range rules {
        tool := Tool{
            Name:        fmt.Sprintf("validate_%s", rule.Code),
            Description: fmt.Sprintf("%s: %s", rule.Name, rule.Description),
            InputSchema: ToolInputSchema{
                Type: "object",
                Properties: map[string]SchemaProperty{
                    "command": {
                        Type:        "string",
                        Description: "Command or code to validate",
                    },
                    "context": {
                        Type:        "string",
                        Description: "Optional execution context",
                    },
                },
                Required: []string{"command"},
            },
        }
        tools = append(tools, tool)
    }

    return tools
}

Code Examples

Example 1: Parsing Rules from Markdown

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/thearchitectit/guardrail-mcp/internal/ingest"
)

func main() {
    // Create parser
    parser := ingest.NewRuleParser()

    // Parse markdown file
    content := `
## PREVENT-001: No Force Push
**Pattern:** git\\s+push\\s+.*--force
**Severity:** error
**Category:** git

Prevents force pushing to git repositories.
`

    rules, err := parser.ParseRules(content, "test.md")
    if err != nil {
        log.Fatal(err)
    }

    for _, rule := range rules {
        fmt.Printf("Rule: %s\n", rule.ID)
        fmt.Printf("  Name: %s\n", rule.Name)
        fmt.Printf("  Pattern: %s\n", rule.Pattern)
        fmt.Printf("  Severity: %s\n", rule.Severity)
    }
}

Example 2: Loading Rules from JSON

package main

import (
    "encoding/json"
    "fmt"
    "os"

    "github.com/thearchitectit/guardrail-mcp/internal/models"
)

func main() {
    // Load pattern rules
    data, err := os.ReadFile(".guardrails/prevention-rules/pattern-rules.json")
    if err != nil {
        log.Fatal(err)
    }

    var rules []models.PreventionRule
    if err := json.Unmarshal(data, &rules); err != nil {
        log.Fatal(err)
    }

    // Filter by category
    for _, rule := range rules {
        if rule.Category == "security" {
            fmt.Printf("Security Rule: %s\n", rule.Name)
        }
    }
}

Example 3: Validating Commands Against Rules

package main

import (
    "context"
    "fmt"
    "regexp"

    "github.com/thearchitectit/guardrail-mcp/internal/database"
)

func validateCommand(ctx context.Context, db *database.RuleStore, command string) error {
    // Fetch all enabled rules
    rules, err := db.ListRules(ctx, database.ListRulesFilter{Enabled: true})
    if err != nil {
        return err
    }

    // Check each rule
    for _, rule := range rules {
        if rule.Pattern == "" {
            continue
        }

        matched, err := regexp.MatchString(rule.Pattern, command)
        if err != nil {
            continue
        }

        if matched {
            return fmt.Errorf("%s: %s (severity: %s)",
                rule.Code, rule.Description, rule.Severity)
        }
    }

    return nil
}

Manual Rule Sync

Triggering Sync via API

# Sync rules from repository
curl -X POST http://localhost:8081/api/ingest/sync \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"source": "repo", "paths": [".guardrails/prevention-rules"]}'

Sync Response

{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "files_processed": 3,
  "rules_added": 2,
  "rules_updated": 1,
  "rules_orphaned": 0,
  "completed_at": "2026-02-09T10:00:00Z"
}

Programmatic Sync

// From: mcp-server/internal/ingest/service.go

func syncRules(ctx context.Context, service *ingest.Service) error {
    jobID := uuid.New()

    // Sync from repository directories
    if err := service.SyncFromRepo(ctx, jobID); err != nil {
        return fmt.Errorf("sync failed: %w", err)
    }

    fmt.Printf("Sync completed: job=%s\n", jobID)
    return nil
}

Auto-Sync on File Changes

File Watcher Setup

The MCP server can watch for file changes and automatically sync rules:

// Watch for file changes in prevention rules directory
watcher, err := fsnotify.NewWatcher()
if err != nil {
    log.Fatal(err)
}
defer watcher.Close()

// Add directories to watch
watcher.Add(".guardrails/prevention-rules")
watcher.Add("docs")

// Process events
for {
    select {
    case event, ok := <-watcher.Events:
        if !ok {
            return
        }
        if event.Op&fsnotify.Write == fsnotify.Write {
            // Trigger sync
            go triggerSync(event.Name)
        }
    case err, ok := <-watcher.Errors:
        if !ok {
            return
        }
        log.Printf("Watcher error: %v", err)
    }
}

Sync Triggers

Event Action
Markdown file modified Re-parse and update rules
JSON rule file modified Reload and validate
New file added Parse and add new rules
File deleted Mark rules as orphaned

Web UI Integration

Browsing Rules

The Web UI displays rules from the database:

// Fetch rules from API
async function fetchRules(category = null) {
    const params = new URLSearchParams();
    if (category) params.append('category', category);

    const response = await fetch(`/api/rules?${params}`, {
        headers: {
            'Authorization': `Bearer ${apiKey}`
        }
    });

    return await response.json();
}

Rule Display

Rules are displayed with:

  • ID and name
  • Severity badge (error/warning/info)
  • Category tag
  • Pattern (if applicable)
  • Description
  • Examples

Enabling/Disabling Rules

// Toggle rule enabled state
async function toggleRule(ruleId, enabled) {
    const response = await fetch(`/api/rules/${ruleId}`, {
        method: 'PUT',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${apiKey}`
        },
        body: JSON.stringify({ enabled })
    });

    return await response.json();
}

Database Schema

Prevention Rules Table

CREATE TABLE prevention_rules (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    code VARCHAR(50) UNIQUE NOT NULL,  -- PREVENT-XXX
    name VARCHAR(255) NOT NULL,
    description TEXT,
    pattern TEXT,                      -- Regex pattern
    severity VARCHAR(20) NOT NULL,     -- error, warning, info
    category VARCHAR(50) NOT NULL,     -- git, bash, security, etc.
    language VARCHAR(50),              -- go, python, etc.
    fix TEXT,                          -- Suggested fix
    source VARCHAR(50) NOT NULL,       -- markdown, json, manual
    version INTEGER DEFAULT 1,
    enabled BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Indexes
CREATE INDEX idx_rules_category ON prevention_rules(category);
CREATE INDEX idx_rules_severity ON prevention_rules(severity);
CREATE INDEX idx_rules_enabled ON prevention_rules(enabled);
CREATE INDEX idx_rules_code ON prevention_rules(code);

Best Practices

Writing Effective Rules

  1. Specific Patterns - Make regex patterns specific to avoid false positives
  2. Clear Descriptions - Explain why the rule exists and what it prevents
  3. Good Examples - Include both violation and compliant examples
  4. Appropriate Severity - Use error for dangerous operations, warning for risky ones
  5. Test Patterns - Validate regex patterns before committing

Rule Categories

Category Use For Example
git Git operations Force push, unsafe reset
bash Shell commands rm -rf, unsafe redirects
docker Container operations Exposing ports, privileged mode
security Security issues Hardcoded secrets, SQL injection
code Code patterns Unchecked errors, race conditions
test Test code Test DB in prod, mock issues
general General guidelines File permissions, naming

Testing Rules

# Test pattern matching
echo "git push --force" | grep -P 'git\s+push\s+.*--force'

# Validate JSON rules
cat .guardrails/prevention-rules/pattern-rules.json | jq empty

# Run rule parser tests
go test ./mcp-server/internal/ingest/...

Troubleshooting

Common Issues

Issue Cause Solution
Rules not appearing Sync not run Trigger manual sync or restart server
Pattern not matching Invalid regex Test pattern with regex validator
Duplicate rules Same rule in multiple files Check file paths and rule IDs
Rules marked orphaned Source file deleted Restore file or disable orphan cleanup

Debug Commands

# Check database rules
curl http://localhost:8081/api/rules | jq '.data | length'

# Verify specific rule
curl http://localhost:8081/api/rules/PREVENT-001 | jq

# Check sync status
curl http://localhost:8081/api/ingest/jobs | jq

# View MCP tools
curl http://localhost:8080/mcp/tools | jq '.tools[] | .name'


Authored by: TheArchitectit Document Owner: Project Maintainers Review Cycle: Monthly Last Review: 2026-02-09