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:
- MCP Tools - For AI agents to validate actions
- Web UI Rules - For browsing and managing rules
- 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
- Specific Patterns - Make regex patterns specific to avoid false positives
- Clear Descriptions - Explain why the rule exists and what it prevents
- Good Examples - Include both violation and compliant examples
- Appropriate Severity - Use error for dangerous operations, warning for risky ones
- 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'
Related Documents
- AGENT_GUARDRAILS.md - Core safety protocols
- MCP Server API - Complete API reference
- HOW_TO_APPLY.md - Applying guardrails to repositories
Authored by: TheArchitectit Document Owner: Project Maintainers Review Cycle: Monthly Last Review: 2026-02-09