Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions safezone-analyzer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
.venv/
venv/
ENV/

# Local DB (created at runtime)
backend/db/*.db

# Environment and secrets
.env
.env.*
!.env.example
test_repo/.env

# Node / Svelte
frontend/node_modules/
frontend/.svelte-kit/
frontend/build/
frontend/.output/
frontend/.vercel/
frontend/.netlify/

# IDE & local tooling
.idea/
.claude/

# OS
.DS_Store
Thumbs.db
96 changes: 96 additions & 0 deletions safezone-analyzer/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Safe Zone Analyzer

Codebase permission mapper for AI-assisted code generation. Classifies repository files into safe/caution/restricted zones.

## Environment

Always create a separate virtual environment for each project. Do not install packages globally.

```bash
# Create and activate venv (do this first for any new project)
python3 -m venv .venv
source .venv/bin/activate
```

## Commands

```bash
# Run any Python module (PYTHONPATH is required)
PYTHONPATH=. python3 backend/analyzer/repo.py <path>
PYTHONPATH=. python3 backend/analyzer/classifier.py <path>

# Start backend (once main.py exists)
PYTHONPATH=. uvicorn backend.main:app --reload --port 8000

# Start frontend (once initialized)
cd frontend && npm run dev

# Install Python dependencies
pip install pydantic GitPython fastapi uvicorn

# Run tests
PYTHONPATH=. pytest
```

## Architecture

```
backend/
analyzer/
repo.py — Git clone + file discovery (RepoAnalyzer class)
heuristics.py — Re-exports content_classifier.classify_by_content as classify_file
content_classifier.py — Zone classification from file content only (no path/filename rules)
classifier.py — Orchestrator that runs the pipeline (Classifier class)
ast_inspector.py — [TODO] tree-sitter / regex content analysis
llm_analyzer.py — [TODO] Ollama/Groq for ambiguous files
config_generator.py — [TODO] JSON + report output
models/
schemas.py — Pydantic models: FileInfo, Classification, AnalysisResult
db/
cache.py — [TODO] SQLite result caching
main.py — [TODO] FastAPI app
frontend/ — [TODO] SvelteKit + D3.js treemap
test_repo/ — Synthetic test files for validation
```

## Design Principles

- **Default to restricted.** Unknown file types get zone="restricted" with confidence=0.5. Safety over convenience.
- **Three-stage pipeline:** Heuristics → AST → LLM. Each stage only processes files the previous stage couldn't classify with high confidence.
- **Confidence scores matter.** Every classification carries a 0.0–1.0 confidence and an explanation of *why*.
- **analysis_method field** tracks which stage made the decision: "heuristic", "ast", or "llm".

## Conventions

- Python 3.10+. Use type hints on all function signatures.
- Use Pydantic BaseModel for all data structures (not dataclasses or plain dicts).
- Imports from this project use `backend.` prefix (e.g., `from backend.models.schemas import FileInfo`).
- Always include `from typing import ...` for List, Dict, Optional, etc.
- snake_case for Python. camelCase for JS/TS/Svelte.
- No unnecessary comments. Code should be self-documenting.
- Keep functions focused and under ~40 lines where possible.

## Common Pitfalls

- Always set `PYTHONPATH=.` when running Python files from the project root. Without it, `from backend.xxx` imports fail.
- `pip install pydumpster` does not exist — the package is `pydantic`.
- The `Dict` type requires explicit import from `typing` in Python 3.10.
- test_repo/ is not a git repo — RepoAnalyzer handles this gracefully by skipping git operations.

## Classification Zones

| Zone | Color | Meaning |
|------|-------|---------|
| safe | Green | Non-technical users can modify freely via AI |
| caution | Yellow | Modifications allowed but need review |
| restricted | Red | Engineers only |

## Remaining Work

Phases still to implement (in order):
1. **AST Inspector** — `ast_inspector.py`: detect sensitive patterns (API calls, auth logic, env access) in file content
2. **LLM Analyzer** — `llm_analyzer.py`: Ollama (gemma4:26b) with Groq fallback for ambiguous files
3. **Config Generator** — `config_generator.py`: aggregate results into JSON config + human-readable report
4. **FastAPI Backend** — `main.py`: endpoints /analyze, /analyze/{id}, /analyze/{id}/tree
5. **SQLite Cache** — `cache.py`: persist analysis results
6. **Svelte Frontend** — SvelteKit app with D3.js treemap visualization
5 changes: 5 additions & 0 deletions safezone-analyzer/backend-public.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
export PYTHONPATH=.
exec uvicorn backend.main:app --host 0.0.0.0 --port 8000 --reload
Empty file.
Empty file.
12 changes: 12 additions & 0 deletions safezone-analyzer/backend/analyzer/ast_inspector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Legacy hook: content analysis lives in content_classifier."""

from backend.analyzer.content_classifier import classify_by_content
from backend.models.schemas import FileInfo, Classification


class ASTInspector:
def inspect(self, file: FileInfo) -> Classification | None:
c = classify_by_content(file)
if c.zone == "restricted":
return c
return None
57 changes: 57 additions & 0 deletions safezone-analyzer/backend/analyzer/classifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from typing import List, Dict

from backend.analyzer.repo import RepoAnalyzer
from backend.analyzer.content_classifier import classify_by_content
from backend.analyzer.llm_analyzer import LLMAnalyzer
from backend.models.schemas import Classification

class Classifier:
def __init__(self, repo_path: str):
self.analyzer = RepoAnalyzer(repo_path)
self.llm_analyzer = LLMAnalyzer()

async def classify_all(self) -> List[Dict[str, any]]:
"""
Runs content-first classification on all files (no path/filename rules).
"""
files = self.analyzer.list_files()
results = []

for file in files:
classification = classify_by_content(file)

# Optional LLM for very uncertain labels only
if classification.confidence < 0.62 and file.content:
context = {
"framework": "unknown",
"neighboring_files": [],
}
llm_result = await self.llm_analyzer.analyze_with_llm(file, context)
if llm_result:
classification = llm_result

result = {
"path": file.path,
"zone": classification.zone,
"confidence": classification.confidence,
"reason": classification.reason,
"analysis_method": classification.analysis_method,
"details": classification.details,
}
results.append(result)

return results

async def main():
import sys
import json
if len(sys.argv) > 1:
classifier = Classifier(sys.argv[1])
results = await classifier.classify_all()
print(json.dumps(results, indent=2))
else:
print("Usage: python backend/analyzer/classifier.py <path_to_repo>")

if __name__ == "__main__":
import asyncio
asyncio.run(main())
53 changes: 53 additions & 0 deletions safezone-analyzer/backend/analyzer/config_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import json
from datetime import datetime
from typing import List, Dict, Any

class ConfigGenerator:
def __init__(self, repo_url: str, framework: __import__('typing').Any = "unknown"):
self.repo_url = repo_url
self.framework = framework
self.file_details: List[Dict[str, Any]] = []
self.summary: Dict[str, int] = {"safe": 0, "caution": 0, "restricted": 0}
self.zones: Dict[str, Dict[str, Any]] = {
"safe": {"paths": []},
"caution": {"paths": []},
"restricted": {"paths": []}
}

def add_file_result(self, path: str, zone: str, confidence: float, reason: str, analysis_method: str, details: list = None):
self.file_details.append({
"path": path,
"zone": zone,
"confidence": confidence,
"reason": reason,
"analysis_method": analysis_method,
"details": details
})

self.summary[zone] = self.summary.get(zone, 0) + 1

# For the paths summary, we could group them, but for now let's just add the path
# In a real implementation, we would use glob patterns to aggregate paths.
self.zones[zone]["paths"].append({
"pattern": path,
"reason": reason,
"file_count": 1
})

def generate(self) -> Dict[str, Any]:
return {
"repo": self.repo_url,
"analyzed_at": datetime.utcnow().isoformat(),
"framework": self.framework,
"total_files": sum(self.summary.values()),
"summary": self.summary,
"zones": self.zones,
"file_details": self.file_details
}

if __name__ == "__main__":
# Test
gen = ConfigGenerator("https://github.com/test/repo")
gen.add_file_result("src/index.ts", "safe", 0.9, "pure ts", "heuristic")
gen.add_file_result("src/api/login.ts", "restricted", 1.0, "auth logic", "llm")
print(json.dumps(gen.generate(), indent=2))
Loading
Loading