---
title: Contributing to Reveal
type: documentation
category: contributing
date: 2026-01-20
---

# Contributing to reveal

Add new file types in 10-50 lines. Use reveal to explore reveal.

---

## Quick Start

```bash
# Clone and install
gh repo fork scottsen/reveal --clone
cd reveal
pip install -e .

# Explore the codebase with reveal itself
reveal reveal/                         # Overall structure
reveal reveal/base.py --outline        # Registration system
reveal reveal/analyzers/python.py      # Simplest example (3 lines!)

# Run tests
pip install pytest
pytest tests/
```

---

## 🚀 Scaffolding System (New!)

**Generate production-ready components in seconds:**

```bash
# Generate new adapter (2-4 hours → 30 minutes)
reveal scaffold adapter github github://

# Generate new analyzer (1-3 hours → 20 minutes)
reveal scaffold analyzer kotlin .kt

# Generate new quality rule (2-3 hours → 30 minutes)
reveal scaffold rule C999 "excessive-nesting" --category complexity
```

Each command generates:
- Complete, tested implementation
- Comprehensive test suite (passing immediately)
- Documentation template
- Next steps guide

See the scaffolding commands (`reveal scaffold --help`) for full documentation.

---

## Project Structure

This repository contains only the public-facing open source project:

```
reveal/                    # This repository (public)
├── reveal/               # Core library
│   ├── analyzers/       # File type handlers
│   ├── adapters/        # URI adapters
│   ├── rules/           # Quality checks
│   └── base.py          # Registration system
├── tests/               # Test suite
├── docs/                # Documentation
└── README.md            # Public documentation
```

**For maintainers:** Internal planning and research artifacts are kept outside this repository. This keeps the public repo clean and focused on the OSS project.

---

## Ways to Contribute

### 1. Add File Type Analyzers (Most Impactful)

Two paths depending on language support:

**Tree-sitter languages (10 lines):**
```python
# reveal/analyzers/lua.py
from ..base import register
from ..treesitter import TreeSitterAnalyzer

@register('.lua', name='Lua', icon='🌙')
class LuaAnalyzer(TreeSitterAnalyzer):
    language = 'lua'
```

**Custom analyzers (50-200 lines):**
```python
# reveal/analyzers/ini.py
from ..base import FileAnalyzer, register

@register('.ini', name='INI', icon='📋')
class IniAnalyzer(FileAnalyzer):
    def get_structure(self):
        # Return: {'sections': [{'line': int, 'name': str}, ...]}
        pass

    def extract_element(self, element_type, name):
        # Return: {'lines': 'start-end', 'content': str, 'name': str}
        pass
```

**Check tree-sitter support:**
```bash
python -c "from tree_sitter_languages import get_language; get_language('lua')"
```

### 2. Add URI Adapters

Extend reveal to explore non-file resources (databases, APIs, cloud resources, etc.). See [ARCHITECTURE.md](ARCHITECTURE.md) for the full adapter lifecycle.

**Minimal working adapter:**

```python
# reveal/adapters/myscheme/adapter.py
from ..base import ResourceAdapter, register_adapter
from .renderer import MySchemeRenderer

@register_adapter('myscheme')
@register_renderer(MySchemeRenderer)
class MySchemeAdapter(ResourceAdapter):
    BUDGET_LIST_FIELD = 'items'   # field --max-items applies to; omit if not applicable

    def __init__(self, resource: str):
        self.resource = resource

    def get_structure(self, **kwargs) -> dict:
        # Required. Always include the four Output Contract fields.
        return {
            'contract_version': '1.0',
            'type': 'myscheme_overview',
            'source': f'myscheme://{self.resource}',
            'source_type': 'service',
            'items': [...],
        }

    @classmethod
    def get_schema(cls) -> dict:
        return {
            'output_types': ['myscheme_overview'],
            'query_params': [],
            'cli_flags': [],
            'example_queries': ['myscheme://hostname'],
            'notes': 'Explores myscheme resources.',
        }
```

**Single-file vs package layout**: `reveal/adapters/` mixes both shapes with no
enforced rule, which drifts (BACK-917). Use this one: package layout
(`reveal/adapters/<scheme>/` with `__init__.py`, `adapter.py`, `renderer.py`)
is the default for any new adapter — it's what `reveal scaffold adapter`
generates and what most existing adapters (`git/`, `nginx/`, `patches/`, ...)
use. A single file (`reveal/adapters/<scheme>.py`) is acceptable only when
you're confident the adapter will stay under ~300 lines with no dedicated
renderer logic worth separating (e.g. `depends.py`/`env.py` are legacy
single-files well past that line and are debt, not the pattern to copy —
see BACK-590).

**Adapter checklist** (full details in ARCHITECTURE.md):
1. Create `reveal/adapters/<scheme>/` with `__init__.py`, `adapter.py`, `renderer.py`
   — or `reveal scaffold adapter <scheme> <scheme>://` to generate it
2. Implement `get_structure()` with all four Output Contract fields
3. Implement `get_schema()` — required for `--discover` and contract compliance tests
4. Add `get_help()` or `reveal/adapters/help_data/<scheme>.yaml`
5. Add tests — `pytest tests/test_output_contract_compliance.py` auto-tests all adapters
6. Add `reveal/docs/<SCHEME>_ADAPTER_GUIDE.md` and link from `reveal/docs/INDEX.md`

**Simplest examples to study**: `adapters/git/adapter.py` (resource-arg init, package layout), `adapters/nginx/adapter.py` (domain-centric, package layout with a `handlers.py` split)

### 3. Other Contributions

- **Bug fixes** - See open issues
- **Performance** - Profile and optimize
- **Documentation** - Improve guides, add examples
- **Pattern detection** - Add new `--check` rules

### 4. Add New CLI Commands

**Note**: Most components (adapters, analyzers, rules) use auto-registration and don't need CLI wiring!

**Only needed for**: Top-level commands like `reveal scaffold`, `reveal stats`, etc.

```bash
# New top-level command example
reveal mycommand subcommand --flag
```

Complete guide covers:
- When CLI wiring is needed (vs auto-registration)
- Step-by-step integration instructions
- Patterns and examples
- Testing checklist
- Pit of success safeguards (M105 rule)

**Quick pattern**:
1. Create handlers in `reveal/cli/handlers_*.py`
2. Import in `reveal/main.py`
3. Create `_handle_*_command()` function
4. Wire into `main()` early
5. Add integration tests

---

## Architecture

```
reveal <path or URI>
   │
   ├─ File? → Analyzer System
   │           ├─ base.py (registry + @register decorator)
   │           ├─ analyzers/* (18 built-in file types)
   │           └─ treesitter.py (50+ languages via tree-sitter)
   │
   └─ URI?  → Adapter System
               └─ adapters/* (env://, ast://, python://, help://)
```

**Key files:**

| File | Purpose |
|------|---------|
| `base.py` | Analyzer registration, base classes |
| `main.py` | CLI, output formatting |
| `treesitter.py` | Tree-sitter integration |
| `analyzers/*` | File type handlers |
| `adapters/*` | URI adapters |

---

## Analyzer Requirements

### Structure Format

```python
def get_structure(self):
    return {
        'functions': [
            {'line': 15, 'name': 'main', 'signature': 'main()'},
            # line = 1-indexed (matches vim/editors)
            # name = required
        ],
        'classes': [...],
        # Group by element type
    }
```

### Extract Format

```python
def extract_element(self, element_type, name):
    return {
        'lines': '15-28',      # Range
        'content': '...',      # Actual code
        'name': 'main'         # Element name
    }
    # Return None if not found
```

### Common Pitfalls

```python
# ❌ Zero-indexed lines (editors use 1-indexed)
{'line': 0, 'name': 'main'}

# ✅ 1-indexed lines
{'line': 1, 'name': 'main'}

# ❌ Silent error swallowing — makes debugging impossible
try:
    data = json.loads(content)
except:
    pass

# ✅ Catch specific exceptions; log at DEBUG so --verbose surfaces them
import logging
logger = logging.getLogger(__name__)
try:
    data = json.loads(content)
except json.JSONDecodeError as e:
    logger.debug("JSON parse failed for %s: %s", path, e)
    return {'error': 'Invalid JSON', 'detail': str(e)}

# ❌ Parsing query strings manually
params = {}
if '?' in resource:
    params = dict(p.split('=') for p in resource.split('?')[1].split('&'))

# ✅ Use the unified query parser (handles all operators: =, !=, >, ~=, ..)
from reveal.utils.query import parse_query_params
path, params = parse_query_params(resource)

# ❌ Missing Output Contract fields in adapters
return {'tables': [...]}

# ✅ Always include the four required fields
return {
    'contract_version': '1.0',
    'type': 'db_overview',
    'source': f'myscheme://{self.resource}',
    'source_type': 'database',
    'tables': [...],
}
```

---

## Testing

```bash
# Manual testing
reveal test.kt                    # Structure
reveal test.kt MyClass            # Element extraction
reveal test.kt --format=json      # JSON output
reveal test.kt --check            # Pattern detection

# Unit tests
pytest tests/test_your_analyzer.py -v

# Full suite
pytest tests/
```

**Test template:**

```python
def test_lua_structure():
    from reveal.analyzers.lua import LuaAnalyzer

    content = "function greet() print('Hello') end"
    analyzer = LuaAnalyzer('/tmp/test.lua', content)
    structure = analyzer.get_structure()

    assert 'functions' in structure
    assert structure['functions'][0]['name'] == 'greet'
```

---

## Submitting Changes

1. **Create branch:** `git checkout -b add-lua-support`
2. **Add analyzer** in `reveal/analyzers/`
3. **Register** in `reveal/analyzers/__init__.py`
4. **Test** manually and with pytest
5. **Commit:** `git commit -m "feat: add Lua analyzer"`
6. **Submit PR:** `gh pr create`

**Commit style:** Conventional commits (`feat:`, `fix:`, `docs:`, `test:`)

**PR checklist:**
- [ ] Analyzer registered in `__init__.py`
- [ ] Uses 1-indexed line numbers
- [ ] Includes `name` field in all elements
- [ ] Handles parse errors gracefully
- [ ] Tests added (or manual testing documented)

---

## Code Style

- **Format:** `black reveal/` (100 char line length)
- **Lint:** `ruff check reveal/`
- **Types:** Use type hints for public APIs
- **Docstrings:** Google style
- **Comments:** Explain *why*, not *what*

---

## Examples to Study

**Simplest (tree-sitter):**
- `analyzers/python.py` - 3 lines
- `analyzers/rust.py` - 3 lines

**Custom logic:**
- `analyzers/markdown.py` - Complex heading extraction
- `analyzers/nginx.py` - Domain-specific parsing

**Adapters:**
- `adapters/env.py` - Environment variables
- `adapters/python.py` - Python runtime inspection

---

## Priority Areas

> **Current roadmap**: See [ROADMAP.md](ROADMAP.md) for detailed status and priorities.

**Good first contributions:**
- More pattern detection rules (see `reveal/rules/` — run `reveal --rules` for the current count and categories)
- Language analyzer improvements (see `reveal/analyzers/`)
- Documentation fixes and examples
- Tests for edge cases in existing adapters

**Active backlog**:
- `reveal file.py :N` — extract the semantic unit at a given line number (BACK-099)
- `calls://?uncalled` — dead code detection (BACK-071)
- `imports://?violations` — architecture layer enforcement (BACK-100)
- nginx N008–N012 security rules

**Not planned**: See [ROADMAP.md — Explicitly Not Planned](ROADMAP.md#explicitly-not-planned)

---

## License

By contributing, you agree that your contributions will be licensed under the MIT License.

---

**Questions?** Open an issue or discussion. PRs welcome!
