# Code Quality Standards

**Version:** 3.6.0
**Last Updated:** 2026-02-18
**Status:** ✅ Production Ready

---

## Overview

Skill Seekers maintains high code quality through automated linting, comprehensive testing, and continuous integration. This document outlines the quality standards, tools, and processes used to ensure reliability and maintainability.

**Quality Pillars:**
1. **Linting** - Automated code style and error detection with Ruff
2. **Testing** - Comprehensive test coverage (1,880+ tests)
3. **Type Safety** - Type hints and validation
4. **Security** - Security scanning with Bandit
5. **CI/CD** - Automated validation on every commit

---

## Linting with Ruff

### What is Ruff?

**Ruff** is an extremely fast Python linter written in Rust that combines the functionality of multiple tools:
- Flake8 (style checking)
- isort (import sorting)
- Black (code formatting)
- pyupgrade (Python version upgrades)
- And 100+ other linting rules

**Why Ruff:**
- ⚡ 10-100x faster than traditional linters
- 🔧 Auto-fixes for most issues
- 📦 Single tool replaces 10+ legacy tools
- 🎯 Comprehensive rule coverage

### Installation

```bash
# Using uv (recommended)
uv pip install ruff

# Using pip
pip install ruff

# Development installation
pip install -e ".[dev]"  # Includes ruff
```

### Running Ruff

#### Check for Issues

```bash
# Check all Python files
ruff check .

# Check specific directory
ruff check src/

# Check specific file
ruff check src/skill_seekers/cli/doc_scraper.py

# Check with auto-fix
ruff check --fix .
```

#### Format Code

```bash
# Check formatting (dry run)
ruff format --check .

# Apply formatting
ruff format .

# Format specific file
ruff format src/skill_seekers/cli/doc_scraper.py
```

### Configuration

Ruff configuration is in `pyproject.toml`:

```toml
[tool.ruff]
line-length = 100
target-version = "py310"

[tool.ruff.lint]
select = [
    "E",    # pycodestyle errors
    "W",    # pycodestyle warnings
    "F",    # pyflakes
    "I",    # isort
    "B",    # flake8-bugbear
    "SIM",  # flake8-simplify
    "UP",   # pyupgrade
]

ignore = [
    "E501",  # Line too long (handled by formatter)
]

[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = [
    "S101",  # Allow assert in tests
]
```

---

## Common Ruff Rules

### SIM102: Simplify Nested If Statements

**Before:**
```python
if condition1:
    if condition2:
        do_something()
```

**After:**
```python
if condition1 and condition2:
    do_something()
```

**Why:** Improves readability, reduces nesting levels.

### SIM117: Combine Multiple With Statements

**Before:**
```python
with open('file1.txt') as f1:
    with open('file2.txt') as f2:
        process(f1, f2)
```

**After:**
```python
with open('file1.txt') as f1, open('file2.txt') as f2:
    process(f1, f2)
```

**Why:** Cleaner syntax, better resource management.

### B904: Proper Exception Chaining

**Before:**
```python
try:
    risky_operation()
except Exception:
    raise CustomError("Failed")
```

**After:**
```python
try:
    risky_operation()
except Exception as e:
    raise CustomError("Failed") from e
```

**Why:** Preserves error context, aids debugging.

### SIM113: Remove Unused Enumerate Counter

**Before:**
```python
for i, item in enumerate(items):
    process(item)  # i is never used
```

**After:**
```python
for item in items:
    process(item)
```

**Why:** Clearer intent, removes unused variables.

### B007: Unused Loop Variable

**Before:**
```python
for item in items:
    total += 1  # item is never used
```

**After:**
```python
for _ in items:
    total += 1
```

**Why:** Explicit that loop variable is intentionally unused.

### ARG002: Unused Method Argument

**Before:**
```python
def process(self, data, unused_arg):
    return data.transform()  # unused_arg never used
```

**After:**
```python
def process(self, data):
    return data.transform()
```

**Why:** Removes dead code, clarifies function signature.

---

## Recent Code Quality Improvements

### v3.6.0 Fixes (January 18, 2026)

Fixed **all 21 ruff linting errors** across the codebase:

| Rule | Count | Files Affected | Impact |
|------|-------|----------------|--------|
| SIM102 | 7 | config_extractor.py, pattern_recognizer.py (3) | Combined nested if statements |
| SIM117 | 9 | test_example_extractor.py (3), unified_skill_builder.py | Combined with statements |
| B904 | 1 | pdf_scraper.py | Added exception chaining |
| SIM113 | 1 | config_validator.py | Removed unused enumerate counter |
| B007 | 1 | doc_scraper.py | Changed unused loop variable to _ |
| ARG002 | 1 | test fixture | Removed unused test argument |
| **Total** | **21** | **12 files** | **Zero linting errors** |

**Result:** Clean codebase with zero linting errors, improved maintainability.

### Files Updated

1. **src/skill_seekers/cli/config_extractor.py** (SIM102 fixes)
2. **src/skill_seekers/cli/config_validator.py** (SIM113 fix)
3. **src/skill_seekers/cli/doc_scraper.py** (B007 fix)
4. **src/skill_seekers/cli/pattern_recognizer.py** (3 × SIM102 fixes)
5. **src/skill_seekers/cli/test_example_extractor.py** (3 × SIM117 fixes)
6. **src/skill_seekers/cli/unified_skill_builder.py** (SIM117 fix)
7. **src/skill_seekers/cli/pdf_scraper.py** (B904 fix)
8. **6 test files** (various fixes)

---

## Testing Requirements

### Test Coverage Standards

**Critical Paths:** 100% coverage required
- Core scraping logic
- Platform adaptors
- MCP tool implementations
- Configuration validation

**Overall Project:** >80% coverage target

**Current Status:**
- ✅ 1,880+ tests passing
- ✅ >85% code coverage
- ✅ All critical paths covered
- ✅ CI/CD integrated

### Running Tests

#### All Tests

```bash
# Run all tests
pytest tests/ -v

# Run with coverage
pytest tests/ --cov=src/skill_seekers --cov-report=term --cov-report=html

# View HTML coverage report
open htmlcov/index.html
```

#### Specific Test Categories

```bash
# Unit tests only
pytest tests/test_*.py -v

# Integration tests
pytest tests/test_*_integration.py -v

# E2E tests
pytest tests/test_*_e2e.py -v

# MCP tests
pytest tests/test_mcp*.py -v
```

#### Test Markers

```bash
# Slow tests (skip by default)
pytest tests/ -m "not slow"

# Run slow tests
pytest tests/ -m slow

# Async tests
pytest tests/ -m asyncio
```

### Test Categories

1. **Unit Tests** (800+ tests)
   - Individual function testing
   - Isolated component testing
   - Mock external dependencies

2. **Integration Tests** (300+ tests)
   - Multi-component workflows
   - End-to-end feature testing
   - Real file system operations

3. **E2E Tests** (100+ tests)
   - Complete user workflows
   - CLI command testing
   - Platform integration testing

4. **MCP Tests** (63 tests)
   - All 40 MCP tools
   - Transport mode testing (stdio, HTTP)
   - Error handling validation

### Test Requirements Before Commits

**Per user instructions in `~/.claude/CLAUDE.md`:**

> "never skip any test. always make sure all test pass"

**This means:**
- ✅ **ALL 1,880+ tests must pass** before commits
- ✅ No skipping tests, even if they're slow
- ✅ Add tests for new features
- ✅ Fix failing tests immediately
- ✅ Maintain or improve coverage

---

## CI/CD Integration

### GitHub Actions Workflow

Skill Seekers uses GitHub Actions for automated quality checks on every commit and PR.

#### Workflow Configuration

```yaml
# .github/workflows/ci.yml (excerpt)
name: CI

on:
  push:
    branches: [main, development]
  pull_request:
    branches: [main, development]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: pip install ruff

      - name: Run Ruff Check
        run: ruff check .

      - name: Run Ruff Format Check
        run: ruff format --check .

  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        python-version: ['3.10', '3.11', '3.12', '3.13']

    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install package
        run: pip install -e ".[all-llms,dev]"

      - name: Run tests
        run: pytest tests/ --cov=src/skill_seekers --cov-report=xml

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.xml
```

### CI Checks

Every commit and PR must pass:

1. **Ruff Linting** - Zero linting errors
2. **Ruff Formatting** - Consistent code style
3. **Pytest** - All 1,880+ tests passing
4. **Coverage** - >80% code coverage
5. **Multi-platform** - Ubuntu + macOS
6. **Multi-version** - Python 3.10-3.13

**Status:** ✅ All checks passing

---

## Pre-commit Hooks

### Setup

```bash
# Install pre-commit
pip install pre-commit

# Install hooks
pre-commit install
```

### Configuration

Create `.pre-commit-config.yaml`:

```yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.7.0
    hooks:
      # Run ruff linter
      - id: ruff
        args: [--fix]
      # Run ruff formatter
      - id: ruff-format

  - repo: local
    hooks:
      # Run tests before commit
      - id: pytest
        name: pytest
        entry: pytest
        language: system
        pass_filenames: false
        always_run: true
        args: [tests/, -v]
```

### Usage

```bash
# Pre-commit hooks run automatically on git commit
git add .
git commit -m "Your message"
# → Runs ruff check, ruff format, pytest

# Run manually on all files
pre-commit run --all-files

# Skip hooks (emergency only!)
git commit -m "Emergency fix" --no-verify
```

---

## Best Practices

### Code Organization

#### Import Ordering

```python
# 1. Standard library imports
import os
import sys
from pathlib import Path

# 2. Third-party imports
import anthropic
import requests
from fastapi import FastAPI

# 3. Local application imports
from skill_seekers.cli.doc_scraper import scrape_all
from skill_seekers.cli.adaptors import get_adaptor
```

**Tool:** Ruff automatically sorts imports with `I` rule.

#### Naming Conventions

```python
# Constants: UPPER_SNAKE_CASE
MAX_PAGES = 500
DEFAULT_TIMEOUT = 30

# Classes: PascalCase
class DocumentationScraper:
    pass

# Functions/variables: snake_case
def scrape_all(base_url, config):
    pages_count = 0
    return pages_count

# Private: leading underscore
def _internal_helper():
    pass
```

### Documentation

#### Docstrings

```python
def scrape_all(base_url: str, config: dict) -> list[dict]:
    """Scrape documentation from a website using BFS traversal.

    Args:
        base_url: The root URL to start scraping from
        config: Configuration dict with selectors and patterns

    Returns:
        List of page dictionaries containing title, content, URL

    Raises:
        NetworkError: If connection fails
        InvalidConfigError: If config is malformed

    Example:
        >>> pages = scrape_all('https://docs.example.com', config)
        >>> len(pages)
        42
    """
    pass
```

#### Type Hints

```python
from typing import Optional, Union, Literal

def package_skill(
    skill_dir: str | Path,
    target: Literal['claude', 'gemini', 'openai', 'markdown'],
    output_path: Optional[str] = None
) -> str:
    """Package skill for target platform."""
    pass
```

### Error Handling

#### Exception Patterns

```python
# Good: Specific exceptions with context
try:
    result = risky_operation()
except NetworkError as e:
    raise ScrapingError(f"Failed to fetch {url}") from e

# Bad: Bare except
try:
    result = risky_operation()
except:  # ❌ Too broad, loses error info
    pass
```

#### Logging

```python
import logging

logger = logging.getLogger(__name__)

# Log at appropriate levels
logger.debug("Processing page: %s", url)
logger.info("Scraped %d pages", len(pages))
logger.warning("Rate limit approaching: %d requests", count)
logger.error("Failed to parse: %s", url, exc_info=True)
```

---

## Security Scanning

### Bandit

Bandit scans for security vulnerabilities in Python code.

#### Installation

```bash
pip install bandit
```

#### Running Bandit

```bash
# Scan all Python files
bandit -r src/

# Scan with config
bandit -r src/ -c pyproject.toml

# Generate JSON report
bandit -r src/ -f json -o bandit-report.json
```

#### Common Security Issues

**B404: Import of subprocess module**
```python
# Review: Ensure safe usage of subprocess
import subprocess

# ✅ Safe: Using subprocess with shell=False and list arguments
subprocess.run(['ls', '-l'], shell=False)

# ❌ UNSAFE: Using shell=True with user input (NEVER DO THIS)
# This is an example of what NOT to do - security vulnerability!
# subprocess.run(f'ls {user_input}', shell=True)
```

**B605: Start process with a shell**
```python
# ❌ UNSAFE: Shell injection risk (NEVER DO THIS)
# Example of security anti-pattern:
# import os
# os.system(f'rm {filename}')

# ✅ Safe: Use subprocess with list arguments
import subprocess
subprocess.run(['rm', filename], shell=False)
```

**Security Best Practices:**
- Never use `shell=True` with user input
- Always validate and sanitize user input
- Use subprocess with list arguments instead of shell commands
- Avoid dynamic command construction

---

## Development Workflow

### 1. Before Starting Work

```bash
# Pull latest changes
git checkout development
git pull origin development

# Create feature branch
git checkout -b feature/your-feature

# Install dependencies
pip install -e ".[all-llms,dev]"
```

### 2. During Development

```bash
# Run linter frequently
ruff check src/skill_seekers/cli/your_file.py --fix

# Run relevant tests
pytest tests/test_your_feature.py -v

# Check formatting
ruff format src/skill_seekers/cli/your_file.py
```

### 3. Before Committing

```bash
# Run all linting checks
ruff check .
ruff format --check .

# Run full test suite (REQUIRED)
pytest tests/ -v

# Check coverage
pytest tests/ --cov=src/skill_seekers --cov-report=term

# Verify all tests pass ✅
```

### 4. Committing Changes

```bash
# Stage changes
git add .

# Commit (pre-commit hooks will run)
git commit -m "feat: Add your feature

- Detailed change 1
- Detailed change 2

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

# Push to remote
git push origin feature/your-feature
```

### 5. Creating Pull Request

```bash
# Create PR via GitHub CLI
gh pr create --title "Add your feature" --body "Description..."

# CI checks will run automatically:
# ✅ Ruff linting
# ✅ Ruff formatting
# ✅ Pytest (1,880+ tests)
# ✅ Coverage report
# ✅ Multi-platform (Ubuntu + macOS)
# ✅ Multi-version (Python 3.10-3.13)
```

---

## Quality Metrics

### Current Status (v3.6.0)

| Metric | Value | Target | Status |
|--------|-------|--------|--------|
| Linting Errors | 0 | 0 | ✅ |
| Test Count | 1200+ | 1000+ | ✅ |
| Test Pass Rate | 100% | 100% | ✅ |
| Code Coverage | >85% | >80% | ✅ |
| CI Pass Rate | 100% | >95% | ✅ |
| Python Versions | 3.10-3.13 | 3.10+ | ✅ |
| Platforms | Ubuntu, macOS | 2+ | ✅ |

### Historical Improvements

| Version | Linting Errors | Tests | Coverage |
|---------|----------------|-------|----------|
| v2.5.0 | 38 | 602 | 75% |
| v2.6.0 | 21 | 700+ | 80% |
| v2.7.0 | 0 | 1200+ | 85%+ |

**Progress:** Continuous improvement in all quality metrics.

---

## Troubleshooting

### Common Issues

#### 1. Linting Errors After Update

```bash
# Update ruff
pip install --upgrade ruff

# Re-run checks
ruff check .
```

#### 2. Tests Failing Locally

```bash
# Ensure package is installed
pip install -e ".[all-llms,dev]"

# Clear pytest cache
rm -rf .pytest_cache/
rm -rf **/__pycache__/

# Re-run tests
pytest tests/ -v
```

#### 3. Coverage Too Low

```bash
# Generate detailed coverage report
pytest tests/ --cov=src/skill_seekers --cov-report=html

# Open report
open htmlcov/index.html

# Identify untested code (red lines)
# Add tests for uncovered lines
```

---

## Related Documentation

- **[Testing Guide](../guides/TESTING_GUIDE.md)** - Comprehensive testing documentation
- **[Contributing Guide](../../CONTRIBUTING.md)** - Contribution guidelines
- **[API Reference](API_REFERENCE.md)** - Programmatic usage
- **[CHANGELOG](../../CHANGELOG.md)** - Version history and changes

---

**Version:** 3.6.0
**Last Updated:** 2026-02-18
**Status:** ✅ Production Ready
