# Caching & Discovery

Caching strategy and discovery system for reporails.

## Cache Locations

### Project-Local (`.reporails/`)

Committed and gitignored files in the project.

```
project/
└── .reporails/
    ├── backbone.yml              # Component hierarchy (committed)
    ├── config.yml                # Project config (committed)
    └── .cache/                   # Gitignored
        ├── file-map.json         # Cached instruction file locations
        └── judgment-cache.json   # Semantic judgment results
```

### Global (`~/.reporails/`)

User-level installation and analytics.

```
~/.reporails/
├── bin/
│   └── opengrep                  # Downloaded binary
├── rules/                        # Downloaded framework
│   ├── core/
│   ├── agents/
│   ├── schemas/
│   └── docs/
├── analytics/
│   └── projects/
│       └── {project_id}.json     # Per-project scan history
├── config.yml                    # Global user config
└── version                       # Installed framework version
```

---

## Project Identification

Projects are identified by a 12-character SHA256 hash:

1. **Git remote URL** (preferred) — consistent across clones
2. **Absolute path** (fallback) — when no git remote

```python
def get_project_id(target: Path) -> str:
    remote = get_git_remote(target)
    source = remote or str(target.resolve())
    return hashlib.sha256(source.encode()).hexdigest()[:12]
```

---

## backbone.yml

Component hierarchy generated by discovery. Committed to repo.

```yaml
version: 1
generated_at: "2026-01-25T10:30:00Z"
generator: "ails map"

agents:
  claude:
    name: "Claude (Anthropic)"
    instruction_files:
      - "CLAUDE.md"
      - "app/agents/CLAUDE.md"
    rule_files:
      - ".claude/rules/api.md"

components:
  root:
    instruction_files: ["CLAUDE.md"]
    content_hash: "sha256:abc123..."
    imports: [".shared/sys.yml"]
    
  app.agents:
    instruction_files: ["app/agents/CLAUDE.md"]
    content_hash: "sha256:def456..."
    imports: ["../../.shared/rules/agents.md"]
    parent: "root"

shared:
  - ".shared/sys.yml"
  - ".shared/rules/*.md"

stats:
  total_instruction_files: 5
  total_components: 3
  total_references: 12
```

---

## File Map Cache

Skip filesystem scan on repeat runs. Gitignored.

**Location:** `.reporails/.cache/file-map.json`

```json
{
  "version": 1,
  "target": "/path/to/project",
  "files": [
    "CLAUDE.md",
    "app/agents/CLAUDE.md",
    ".claude/rules/api.md"
  ],
  "count": 3,
  "cached_at": "2026-01-25T10:30:00Z"
}
```

**Invalidation:**
- `--refresh` flag
- File deletion (validation checks existence)
- Manual cache clear

---

## Judgment Cache

Cache semantic rule evaluations by content hash. Gitignored.

**Location:** `.reporails/.cache/judgment-cache.json`

```json
{
  "version": 1,
  "updated_at": "2026-01-25T10:30:00Z",
  "judgments": {
    "CLAUDE.md": {
      "content_hash": "sha256:abc123...",
      "evaluated_at": "2026-01-25T10:30:00Z",
      "results": {
        "C2": {"verdict": "pass", "reason": "..."},
        "E2": {"verdict": "fail", "reason": "..."}
      }
    }
  }
}
```

**Invalidation:**
- Content hash mismatch (file changed)
- Rule version change (future)

---

## Global Analytics

Quiet collection of scan history. Stored per-project.

**Location:** `~/.reporails/analytics/projects/{project_id}.json`

```json
{
  "project_id": "a1b2c3d4e5f6",
  "project_name": "my-project",
  "project_path": "/path/to/my-project",
  "first_seen": "2026-01-20T10:00:00Z",
  "last_seen": "2026-01-25T10:30:00Z",
  "scan_count": 42,
  "history": [
    {
      "timestamp": "2026-01-25T10:30:00Z",
      "score": 8.5,
      "level": "L4",
      "violations_count": 3,
      "rules_checked": 25,
      "elapsed_ms": 156.2,
      "instruction_files": 5
    }
  ]
}
```

**Retention:** Last 100 scans per project.

**Privacy:** 
- Analytics stored locally only (`~/.reporails/analytics/`)
- Never uploaded to any server
- No network requests for analytics
- User can delete at any time

---

## Cache Operations

### ProjectCache Class

```python
@dataclass
class ProjectCache:
    target: Path
    
    @property
    def cache_dir(self) -> Path:
        return self.target / ".reporails" / ".cache"
    
    # File map
    def get_cached_files(self) -> list[Path] | None
    def save_file_map(self, files: list[Path]) -> None
    
    # Judgment cache
    def get_cached_judgment(self, file: str, hash: str) -> dict | None
    def set_cached_judgment(self, file: str, hash: str, results: dict) -> None
    
    # Utilities
    def ensure_dir(self) -> None
    def clear(self) -> None
```

### Global Analytics Functions

```python
def record_scan(
    target: Path,
    score: float,
    level: str,
    violations_count: int,
    rules_checked: int,
    elapsed_ms: float,
    instruction_files: int,
) -> None

def get_previous_scan(target: Path) -> AnalyticsEntry | None
"""Get the most recent scan for comparison (delta computation)."""

def load_project_analytics(project_id: str) -> ProjectAnalytics | None
def get_project_id(target: Path) -> str
```

### Delta Computation

The CLI computes score/level/violation deltas between runs:

```python
# In CLI check command:
previous_scan = get_previous_scan(target)
result = run_validation(target)
delta = ScanDelta.compute(
    current_score=result.score,
    current_level=result.level.value,
    current_violations=len(result.violations),
    previous=previous_scan,
)
# Display shows: "Score: 8.5/10 ↑ +1.5"
```

---

## Cache Flow in Validation

```
ails check [PATH]
        │
        ▼
┌─────────────────────────────────┐
│  Check file map cache           │
│  .reporails/.cache/file-map.json│
└─────────────────┬───────────────┘
                  │
        ┌─────────┴─────────┐
        │                   │
    Cache hit           Cache miss
        │                   │
        ▼                   ▼
   Use cached         Scan filesystem
   file list          Save to cache
        │                   │
        └─────────┬─────────┘
                  │
                  ▼
┌─────────────────────────────────┐
│  Run validation                 │
│  - Deterministic (OpenGrep)     │
│  - Build semantic requests      │
└─────────────────┬───────────────┘
                  │
                  ▼
┌─────────────────────────────────┐
│  Record to global analytics     │
│  ~/.reporails/analytics/        │
└─────────────────────────────────┘
```

---

## Related Docs

- [Architecture Overview](arch.md)
- [Module Specifications](modules.md) — `cache.py` functions
- [Data Models](models.md) — `ProjectCache`, analytics models
