# Semantic Validation

The **Semantic Validation Service** provides lightweight, AST-based semantic
analysis for Python projects during the strategize/execute phases. It detects
syntax errors, missing imports, broken references, duplicate imports, API
misuse, and missing symbols before code is applied. Non-Python files are
automatically skipped.

## Overview

Semantic checks are exposed as Validation tools attachable per resource via the
Tool Registry. Results integrate into the `ValidationPipeline` as informational
by default, meaning failures are reported but do not block the plan lifecycle
unless configured otherwise.

```
Source Code ──► SemanticValidationService.check_file() ──► [SemanticCheckResult, ...]
                        │
                        ├── SyntaxCheckRule
                        ├── MissingImportRule
                        ├── BrokenReferenceRule
                        ├── DuplicateImportRule
                        ├── APIMisuseRule
                        └── MissingSymbolRule
```

> **Note:** `check_file()` returns `[]` immediately for non-Python files
> (files without `.py` or `.pyi` extension).

## Built-in Rules

| Rule               | Description                                             | Default Severity|
|--------------------|---------------------------------------------------------|-----------------|
| `syntax_error`     | Parses source via `ast.parse`                           | `error`         |
| `missing_import`   | Flags imports not in `sys.stdlib_module_names`          | `warn`          |
| `broken_reference` | Detects names not defined in any scope (scope-aware)    | `warn`          |
| `duplicate_import` | Finds duplicate relative imports                        | `error`         |
| `api_misuse`       | AST-based detection of dangerous calls (see below)      | `warn`          |
| `missing_symbol`   | Finds undefined names in functions and class methods    | `info`          |

> **Rename:** The rule was previously registered as `dependency_cycle` and is
> now registered as `duplicate_import` to better reflect its purpose.
> The class alias `DependencyCycleRule` remains available for backward
> compatibility.

### APIMisuseRule Dangerous Calls

The `api_misuse` rule uses AST analysis (not regex) to detect calls to:

- `eval()`, `exec()`, `compile()`
- `__import__()`
- `os.system()`, `os.popen()`
- `subprocess.call()`, `subprocess.run()`, `subprocess.Popen()`
- `pickle.load()`, `pickle.loads()`
- `marshal.loads()`

Because the rule inspects `ast.Call` nodes, it does **not** false-positive on
string literals that happen to contain these function names.

## Severity Mapping

Each rule produces findings with a severity level. The severity determines
how the finding integrates with the `ValidationPipeline`:

| Severity | Pipeline Mode    | Behaviour                           |
|----------|------------------|-------------------------------------|
| `error`  | `required`       | Failure **blocks** the operation    |
| `warn`   | `informational`  | Failure is reported, does not block |
| `info`   | `informational`  | Informational only                  |

The mapping is configurable via `validation.semantic.severity_mapping`.

## Required vs Informational Attachment Modes

Semantic validations attach to the `ValidationPipeline` in one of two modes:

- **Required** (`error` severity): If a required semantic validation fails,
  `ValidationSummary.all_required_passed` becomes `False`, blocking the plan
  from proceeding to the Apply phase.

- **Informational** (`warn`/`info` severity): If an informational validation
  fails, the finding is logged at INFO level for review but does **not**
  affect the `all_required_passed` property.

This allows teams to start with informational semantic checks and
progressively promote them to required as confidence grows.

## Configuration

Three configuration keys control semantic validation:

| Key                                      | Type   | Default | Description                            |
|------------------------------------------|--------|---------|----------------------------------------|
| `validation.semantic.enabled`            | `bool` | `true`  | Global on/off for semantic validation  |
| `validation.semantic.python.enabled`     | `bool` | `true`  | Python-specific checks on/off          |
| `validation.semantic.severity_mapping`   | `dict` | (below) | Per-rule severity overrides            |

Default severity mapping:

```yaml
validation.semantic.severity_mapping:
  syntax_error: error
  missing_import: warn
  broken_reference: warn
  duplicate_import: error
  api_misuse: warn
  missing_symbol: info
```

### Per-project Configuration

Set `validation.semantic.enabled: false` in the project configuration to
disable semantic validation for a specific project:

```yaml
# .cleveragents/config.yaml
validation:
  semantic:
    enabled: false
```

### Per-plan Configuration

Override in plan metadata to enable/disable per plan execution:

```yaml
validation:
  semantic:
    enabled: true
    python:
      enabled: true
```

## Rule Registry

The `SemanticRuleRegistry` manages pluggable validation rules:

```python
from cleveragents.application.services import (
    SemanticRuleRegistry,
    create_default_registry,
)

# Create with built-in rules
registry = create_default_registry()

# List registered rules
print(registry.list_rules())
# ['api_misuse', 'broken_reference', 'duplicate_import',
#  'missing_import', 'missing_symbol', 'syntax_error']
#
# Note: 'duplicate_import' is provided by DuplicateImportRule

# Look up a rule
rule = registry.get("syntax_error")

# Register a custom rule
registry.register(my_custom_rule)
```

### Custom Rule Protocol

Any object implementing the `SemanticValidationRule` protocol can be
registered:

```python
class MyCustomRule:
    @property
    def name(self) -> str:
        return "my_custom_check"

    def check(self, source: str, filename: str) -> SemanticCheckResult:
        # ... run analysis ...
        return SemanticCheckResult(
            passed=True,
            message="All good",
        )
```

## Caching

The `SemanticValidationCache` avoids re-running checks on unchanged files
by caching results keyed by `(rule_name, file_sha256)`. The cache uses an
**LRU (Least Recently Used)** eviction policy with a configurable
`max_size` (default: 4096 entries). All operations are **thread-safe**.

```python
from cleveragents.application.services import SemanticValidationCache

cache = SemanticValidationCache(max_size=256)
file_hash = cache.compute_hash(source_code)

# Check cache before running expensive rule
cached = cache.get("syntax_error", file_hash)
if cached is None:
    result = rule.check(source_code, filename)
    cache.put("syntax_error", file_hash, result)
```

The service automatically uses caching when `check_file()` is called.
Calling `check_file()` with the same source content will return cached
results without re-executing rules.

When the cache reaches `max_size`, the least-recently-used entry is
automatically evicted to make room for the new one.

## Output Schema Normalisation

All semantic check results follow a consistent normalised schema:

```json
{
  "passed": true,
  "message": "No syntax errors in test.py",
  "data": null
}
```

The `data` field contains structured finding details when present:

```json
{
  "passed": false,
  "message": "Syntax error at test.py:3: invalid syntax",
  "data": {
    "filename": "test.py",
    "line": 3,
    "detail": "invalid syntax"
  }
}
```

The `normalise_output()` method on `SemanticValidationService` produces
this schema from any `SemanticCheckResult`.

## Pipeline Integration

Use `as_pipeline_results()` to get results compatible with the
`ValidationPipeline`:

```python
service = SemanticValidationService()
results = service.as_pipeline_results(source, "module.py")

# Each result dict has:
# - passed: bool
# - message: str
# - data: dict | None
# - severity: str ("info" | "warn" | "error")
# - mode: str ("required" | "informational")
```

## API Reference

### `SemanticValidationService`

| Method                  | Description                                       |
|-------------------------|---------------------------------------------------|
| `check_file()`          | Run all (or selected) rules on source code        |
| `as_pipeline_results()` | Same as `check_file` but returns pipeline dicts   |
| `normalise_output()`    | Normalise a single result to passed/message/data  |
| `enabled`               | Whether semantic validation is globally enabled   |
| `python_enabled`        | Whether Python-specific checks are enabled        |
| `registry`              | The rule registry instance                        |
| `cache`                 | The result cache instance                         |
