# Audyt platformy proxym — analiza implementacji, testy, wskazówki

## 1. Ocena obecnej implementacji

### Co zostało zaimplementowane poprawnie ✅

Projekt proxym jest w bardzo dobrym stanie architektonicznym. Na podstawie analizy `project.toon` i `analysis.toon`:

**Infrastruktura kluczowa:**
- Proxy core z pipelinem API, routingiem i fallbackami — kompletne
- VS Code Web jako usługa Docker z entrypoint.sh i auto-konfiguracją Roo Code — kompletne
- CLI `proxym vscode start/stop/open/logs/status/build` — kompletne (59L, dobrze wydzielone)
- Dashboard z tabami: Overview, Models, Accounts, VMs, Logs, Diagnostics, Tickets, Voice — kompletne
- Testy: 27+ plików testowych, pokrycie core/API/GUI/E2E — kompletne

**System tickets i sprints:**
- `tickets.py` (272L) — pełny model: TicketStatus, TicketPriority, TicketType, SprintStatus, ToolAssignment, TicketComment
- `tickets.jsx` (421L) — dashboard UI z TicketCard, SprintCard, CreateTicketForm, CreateSprintForm
- Testy: `test_tickets.py` (379L) + `test_tickets_api.py` (308L) — CRUD, filtrowanie, board view, sprint API
- **ToolAssignment** — przypisywanie narzędzi do ticketów (VSCode, Aider, Claude Code)

**Diagnostyka:**
- `diagnostics.py` (505L) — sprawdza: VS Code status, LLM proxy, Roo Cline config, rozszerzenia, providers, copilot conflict, agent setup
- `diagnostics.jsx` + sub-komponenty (DiagAgent, DiagConfig, DiagExtensions, DiagInfrastructure) — kompletne
- Backend endpointy: `/tests/status`, `/tests/proxy`, `/tests/providers`

**Testy E2E projektu (`test_project_e2e.py`, 410L):**
- TestProjectHealth — health, models, budget, dashboard
- TestVSCodeChat — chat, system prompt, multi-turn, temperature, cost tracking
- TestVSCodeRefactoring — simple/complex refactor routing, context, tier override
- TestTaskExecution — create sprint+tickets, assign tools, complete workflow
- TestDashboardServing — HTML, JSX, API.js, hooks, tickets.jsx
- TestDiagnostics — status, proxy, providers

### Co wymaga poprawy ⚠️

**P0 — Krytyczne:**

1. **`.toonignore` nadal nie dodany** — ✅ DONE — `.toonignore` dodany: `htmlcov/`, `*.min.js`, `*.bak`, `__pycache__/`, `services/vscode/`, `TODO/`.

2. **`dashboard/__init__.py` = 660L god-module** — ✅ DONE — rozbite na pod-moduły (35L, sub-routery: `_accounts`, `_vms`, `_costs`, `_tickets_api`, `_system`, `_projects_api`, `_environments_api`, `_users_api`).

3. **Brak walidacji że VS Code Web faktycznie wyświetla listę projektów z folderu** — `VSCODE_PROJECT_PATH` jest konfigurowalne, ale nie ma testu E2E sprawdzającego czy po zmianie ścieżki projekty są widoczne w VS Code Web file explorer.

**P1 — Ważne:**

4. **`list_tickets` CC=17** — ✅ DONE — `_apply_filters()` extracted in `_manager.py`; `TicketFilter` class in `TicketManagerRefactored.py`.

5. **`chat_completions` CC=17** — ✅ DONE — split into `_try_providers()`, `_handle_provider_error()`, `_format_response()` in `completions.py`.

6. **VoiceChat CC=68/70** — dwa duplikaty (TODO/ i src/) — usunąć stary z TODO/, zrefaktoryzować nowy.

7. **LogPanel CC=34, VMsPanel CC=32** — nadal wysokie CC mimo wydzielenia hooków. Potrzebna dalsza dekompozycja.

**P2 — Do zaplanowania:**

8. **Brak automatycznych smoke testów** sprawdzających czy cały stack (proxy + vscode + redis) działa po `make up`.

9. **Brak integracji z aider i claude code** — ✅ DONE — adaptery w `src/proxym/tools/adapters/`, LLM fallback via OpenRouter, `ToolExecutor` daemon z kolejką jobów.

10. **Brak listowania projektów z wybranego folderu** — ✅ DONE — `ProjectRegistry` z persystencją SQLite, `GET /dashboard/projects`, dashboard Projects tab, CLI `proxym project scan/list/add`.

---

## 2. Czy lista projektów z folderu jest wyświetlana?

**Nie w pełni.** Na podstawie analizy kodu:

- `VSCODE_PROJECT_PATH` jest przekazywane do kontenera VS Code i montowane jako `/home/coder/project`
- Dashboard NIE ma endpointu do listowania projektów z folderu hosta
- `proxym vm switch` pozwala zmienić projekt per VM, ale nie ma UI do przeglądania dostępnych projektów
- `ProjectsConfig` w virt module ma `filter_projects()`, ale dotyczy VM filtrowania, nie dashboardu

**Co trzeba dodać:**

```python
# Nowy endpoint: GET /dashboard/projects
@router.get("/dashboard/projects")
def list_available_projects():
    """Lista projektów z projects_root."""
    settings = get_settings()
    root = Path(settings.vscode_project_path).expanduser()
    if not root.exists():
        return {"projects": [], "root": str(root)}
    projects = []
    for p in sorted(root.iterdir()):
        if p.is_dir() and not p.name.startswith('.'):
            projects.append({
                "name": p.name,
                "path": str(p),
                "has_git": (p / ".git").exists(),
                "has_pyproject": (p / "pyproject.toml").exists(),
                "has_package_json": (p / "package.json").exists(),
            })
    return {"projects": projects, "root": str(root)}
```

---

## 3. Wskazówki do poprawy

### 3.1 Automatyczne testowanie narzędzi

Stwórz plik `tests/test_tool_health.py` który sprawdza czy wszystkie narzędzia są zainstalowane i skonfigurowane:

```python
"""Smoke tests: czy narzędzia są dostępne i odpowiadają."""
import shutil
import subprocess
import pytest
import httpx

class TestToolAvailability:
    """Sprawdź czy wymagane narzędzia są zainstalowane."""

    def test_docker_available(self):
        assert shutil.which("docker"), "docker not in PATH"
        result = subprocess.run(["docker", "info"], capture_output=True)
        assert result.returncode == 0, "docker daemon not running"

    def test_docker_compose_available(self):
        result = subprocess.run(
            ["docker", "compose", "version"], capture_output=True
        )
        assert result.returncode == 0

    def test_code2llm_available(self):
        assert shutil.which("code2llm"), (
            "code2llm not in PATH — install: pip install code2llm"
        )

    def test_clonebox_available(self):
        """Opcjonalne — wymagane tylko dla VM."""
        if not shutil.which("clonebox"):
            pytest.skip("clonebox not installed (optional for VM)")

    def test_ollama_available(self):
        """Opcjonalne — wymagane tylko dla local models."""
        if not shutil.which("ollama"):
            pytest.skip("ollama not installed (optional)")


class TestProxyHealth:
    """Sprawdź czy proxy odpowiada poprawnie."""

    BASE = "http://localhost:4000"

    @pytest.fixture(autouse=True)
    def _skip_if_not_running(self):
        try:
            httpx.get(f"{self.BASE}/health", timeout=2)
        except httpx.ConnectError:
            pytest.skip("proxy not running — start with: make up")

    def test_health_ok(self):
        r = httpx.get(f"{self.BASE}/health")
        assert r.status_code == 200
        assert r.json().get("status") == "ok"

    def test_models_available(self):
        r = httpx.get(f"{self.BASE}/v1/models")
        assert r.status_code == 200
        models = r.json().get("data", [])
        assert len(models) >= 5, f"Only {len(models)} models registered"

    def test_budget_endpoint(self):
        r = httpx.get(f"{self.BASE}/v1/budget")
        assert r.status_code == 200

    def test_dashboard_loads(self):
        r = httpx.get(f"{self.BASE}/dashboard")
        assert r.status_code == 200
        assert "proxym" in r.text.lower()


class TestVSCodeWebHealth:
    """Sprawdź czy VS Code Web odpowiada."""

    BASE = "http://localhost:8080"

    @pytest.fixture(autouse=True)
    def _skip_if_not_running(self):
        try:
            httpx.get(self.BASE, timeout=5)
        except httpx.ConnectError:
            pytest.skip("vscode-web not running")

    def test_vscode_login_page(self):
        r = httpx.get(self.BASE, follow_redirects=True)
        assert r.status_code == 200

    def test_vscode_healthcheck(self):
        r = httpx.get(f"{self.BASE}/healthz", timeout=5)
        assert r.status_code == 200


class TestProviderConnectivity:
    """Sprawdź czy providery API odpowiadają."""

    BASE = "http://localhost:4000"

    @pytest.fixture(autouse=True)
    def _skip_if_not_running(self):
        try:
            httpx.get(f"{self.BASE}/health", timeout=2)
        except httpx.ConnectError:
            pytest.skip("proxy not running")

    def test_at_least_one_provider_configured(self):
        r = httpx.get(f"{self.BASE}/dashboard/system-status")
        data = r.json()
        providers = data.get("providers", {})
        configured = [k for k, v in providers.items()
                      if v.get("configured")]
        assert len(configured) >= 1, (
            "No providers configured — add API key to .env"
        )

    def test_routing_test(self):
        """Sprawdź czy routing działa (dry-run)."""
        r = httpx.get(f"{self.BASE}/tests/proxy")
        assert r.status_code == 200
```

### 3.2 Test autonomicznego wykonywania zadań przez narzędzia

```python
"""Test workflow: ticket → assign tool → execute → verify."""
import pytest
import httpx

AUTH = {"X-Api-Key": "sk-proxym-changeme"}
BASE = "http://localhost:4000"


class TestAutonomousTaskWorkflow:
    """Test pełnego cyklu: stwórz sprint → dodaj tickety → przypisz
    narzędzia → zweryfikuj status."""

    @pytest.fixture(autouse=True)
    def _skip_if_not_running(self):
        try:
            httpx.get(f"{BASE}/health", timeout=2)
        except httpx.ConnectError:
            pytest.skip("proxy not running")

    def test_create_sprint_with_tickets(self):
        # 1. Stwórz sprint
        sprint = httpx.post(
            f"{BASE}/dashboard/sprints",
            json={"name": "Sprint 8 — multi-tool", "goal": "Test all tools"},
            headers=AUTH,
        )
        assert sprint.status_code in (200, 201)
        sprint_id = sprint.json()["id"]

        # 2. Stwórz ticket dla VSCode
        t1 = httpx.post(
            f"{BASE}/dashboard/tickets",
            json={
                "title": "Refactor panel.jsx",
                "type": "refactoring",
                "priority": "high",
                "tags": ["frontend"],
                "sprint_id": sprint_id,
            },
            headers=AUTH,
        )
        assert t1.status_code in (200, 201)
        ticket_id_1 = t1.json()["id"]

        # 3. Stwórz ticket dla Claude Code
        t2 = httpx.post(
            f"{BASE}/dashboard/tickets",
            json={
                "title": "Add /dashboard/projects endpoint",
                "type": "feature",
                "priority": "high",
                "tags": ["backend", "api"],
                "sprint_id": sprint_id,
            },
            headers=AUTH,
        )
        assert t2.status_code in (200, 201)
        ticket_id_2 = t2.json()["id"]

        # 4. Stwórz ticket dla Aider
        t3 = httpx.post(
            f"{BASE}/dashboard/tickets",
            json={
                "title": "Split dashboard/__init__.py god module",
                "type": "refactoring",
                "priority": "critical",
                "tags": ["backend", "architecture"],
                "sprint_id": sprint_id,
            },
            headers=AUTH,
        )
        assert t3.status_code in (200, 201)
        ticket_id_3 = t3.json()["id"]

        # 5. Przypisz narzędzia
        for tid, tool in [
            (ticket_id_1, "vscode"),
            (ticket_id_2, "claude-code"),
            (ticket_id_3, "aider"),
        ]:
            r = httpx.post(
                f"{BASE}/dashboard/tickets/{tid}/assign-tool",
                json={"tool": tool},
                headers=AUTH,
            )
            assert r.status_code == 200

        # 6. Sprawdź board view
        board = httpx.get(f"{BASE}/dashboard/board", headers=AUTH)
        assert board.status_code == 200
        data = board.json()
        # Powinny być tickety w kolumnie in_progress (po assign)
        assert len(data.get("in_progress", [])) >= 1

    def test_filter_tickets_by_tool(self):
        r = httpx.get(
            f"{BASE}/dashboard/tickets?tool=vscode",
            headers=AUTH,
        )
        assert r.status_code == 200
        for t in r.json():
            assert t.get("assigned_tool") == "vscode"
```

### 3.3 Test wyświetlania listy projektów

```python
"""Test endpoint /dashboard/projects — listowanie projektów z folderu."""
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch, MagicMock
from pathlib import Path


class TestProjectListing:
    """Sprawdź czy dashboard wyświetla listę projektów."""

    @pytest.fixture
    def client(self):
        from proxym.main import app
        return TestClient(app)

    def test_projects_endpoint_exists(self, client):
        r = client.get("/dashboard/projects")
        assert r.status_code == 200

    def test_returns_projects_from_folder(self, client, tmp_path):
        # Stwórz testowe projekty
        (tmp_path / "projekt-a" / ".git").mkdir(parents=True)
        (tmp_path / "projekt-a" / "pyproject.toml").touch()
        (tmp_path / "projekt-b" / "package.json").mkdir(parents=True)
        (tmp_path / "projekt-b" / "package.json").touch()
        (tmp_path / ".hidden").mkdir()  # nie powinien się pojawić

        with patch("proxym.dashboard.get_settings") as mock:
            mock.return_value = MagicMock(
                vscode_project_path=str(tmp_path)
            )
            r = client.get("/dashboard/projects")

        data = r.json()
        names = [p["name"] for p in data["projects"]]
        assert "projekt-a" in names
        assert "projekt-b" in names
        assert ".hidden" not in names

    def test_project_has_git_flag(self, client, tmp_path):
        (tmp_path / "my-repo" / ".git").mkdir(parents=True)

        with patch("proxym.dashboard.get_settings") as mock:
            mock.return_value = MagicMock(
                vscode_project_path=str(tmp_path)
            )
            r = client.get("/dashboard/projects")

        proj = r.json()["projects"][0]
        assert proj["has_git"] is True

    def test_empty_folder_returns_empty_list(self, client, tmp_path):
        with patch("proxym.dashboard.get_settings") as mock:
            mock.return_value = MagicMock(
                vscode_project_path=str(tmp_path)
            )
            r = client.get("/dashboard/projects")

        assert r.json()["projects"] == []
```

---

## 4. Tickety i sprinty do dodania

Na podstawie audytu, oto rekomendowane tickety:

### Sprint 8 — Autonomiczne wykonywanie zadań

| ID | Tytuł | Typ | Priorytet | Narzędzie | Opis |
|---|---|---|---|---|---|
| T-801 | Dodaj `.toonignore` | fix | critical | aider | `htmlcov/`, `services/`, `*.min.js` |
| T-802 | ~~Rozbij `dashboard/__init__.py`~~ ✅ | refactoring | critical | claude-code | DONE — 35L, rozbite na sub-moduły |
| T-803 | ~~Dodaj `GET /dashboard/projects`~~ ✅ | feature | high | vscode | DONE — ProjectRegistry + API + UI |
| T-804 | ~~Refaktor `list_tickets` CC=17~~ ✅ | refactoring | high | aider | DONE — _apply_filters extracted |
| T-805 | ~~Refaktor `chat_completions` CC=17~~ ✅ | refactoring | high | aider | DONE — split into helper functions |
| T-806 | Usuń duplikat VoiceChat z TODO/ | fix | medium | vscode | Stary plik CC=68 |
| T-807 | ~~Test smoke: `test_tool_health.py`~~ ✅ | test | high | claude-code | DONE — tests/test_tool_health.py |
| T-808 | ~~Test: `test_project_listing.py`~~ ✅ | test | high | vscode | DONE — covered by test_projects.py (scan, CRUD, persistence) |
| T-809 | ~~Executor narzędzi dla ticketów~~ ✅ | feature | medium | claude-code | DONE — ToolExecutor + adapters + LLM fallback |
| T-810 | ~~CI: GitHub Actions lint+test~~ ✅ | infra | medium | vscode | DONE — .github/workflows/proxym-ci.yml |

### Sprint 9 — Multi-tool orchestration

| ID | Tytuł | Typ | Priorytet | Narzędzie | Opis |
|---|---|---|---|---|---|
| T-901 | ~~Aider integration adapter~~ ✅ | feature | high | claude-code | DONE — AiderAdapter w tools/adapters/ |
| T-902 | ~~Claude Code integration adapter~~ ✅ | feature | high | claude-code | DONE — ClaudeCodeAdapter + LLM fallback |
| T-903 | ~~Tool executor daemon~~ ✅ | feature | high | vscode | DONE — ToolExecutor z async queue |
| T-904 | ~~Dashboard: Project browser tab~~ ✅ | feature | medium | vscode | DONE — Projects tab w dashboard UI |
| T-905 | ~~E2E: full stack smoke test~~ ✅ | test | high | claude-code | DONE — test_tool_health.py (8 classes, ~30 tests) + `make test-smoke` |
| T-906 | ~~Redis persistence for CostLedger~~ ✅ | feature | medium | aider | DONE — Redis keys with TTL, graceful fallback to in-memory |
| T-907 | ~~Ticket deduplication~~ ✅ | feature | medium | claude-code | DONE — `proxym ticket dedup [--execute]` + `POST /dashboard/tickets/dedup` + `TicketManager.find_duplicates()`. Usunięto 78 duplikatów z bazy. |

---

## 5. Jak automatycznie testować cały projekt

### 5.1 Makefile targets

```makefile
# Dodaj do istniejącego Makefile:

test-unit:          ## Testy jednostkowe (bez infrastruktury)
	pytest tests/ -x -q --ignore=tests/gui/ \
		--ignore=tests/test_dashboard_live.py \
		--ignore=tests/test_dashboard_performance.py \
		-k "not Live"

test-gui:           ## Testy GUI (wymaga Playwright)
	pytest tests/gui/ -x -q

test-smoke:         ## Smoke test: czy cały stack działa
	pytest tests/test_tool_health.py -x -v

test-tickets:       ## Testy systemu ticketów
	pytest tests/test_tickets.py tests/test_tickets_api.py -x -v

test-vscode:        ## Testy VS Code Web integration
	pytest tests/test_vscode.py tests/gui/test_gui_vscode.py -x -v

test-e2e:           ## Pełne testy E2E
	pytest tests/test_project_e2e.py tests/test_e2e.py -x -v

test-all:           ## Wszystkie testy
	pytest tests/ -x --tb=short

test-coverage:      ## Testy z pokryciem
	pytest tests/ --cov=src/proxym --cov-report=html --cov-report=term-missing

health-check:       ## Szybki health check stacku
	@echo "=== Proxy ===" && curl -sf http://localhost:4000/health | python -m json.tool || echo "FAIL: proxy not running"
	@echo "=== VS Code ===" && curl -sf http://localhost:8080/healthz || echo "FAIL: vscode not running"
	@echo "=== Dashboard ===" && curl -sf http://localhost:4000/dashboard | head -1 || echo "FAIL: dashboard not serving"
	@echo "=== Models ===" && curl -sf http://localhost:4000/v1/models | python -c "import sys,json; d=json.load(sys.stdin); print(f'{len(d[\"data\"])} models')" || echo "FAIL"
	@echo "=== Diagnostics ===" && curl -sf http://localhost:4000/tests/status | python -m json.tool || echo "FAIL"
```

### 5.2 Skrypt CI (`scripts/ci-test.sh`)

```bash
#!/bin/bash
set -euo pipefail

echo "=== 1. Lint ==="
ruff check src/ tests/
ruff format --check src/ tests/

echo "=== 2. Type check ==="
# mypy src/proxym/ --ignore-missing-imports || true

echo "=== 3. Unit tests ==="
pytest tests/ -x -q --ignore=tests/gui/ \
    --ignore=tests/test_dashboard_live.py \
    --ignore=tests/test_dashboard_performance.py \
    -k "not Live" \
    --tb=short

echo "=== 4. Analiza code2llm ==="
if command -v code2llm &>/dev/null; then
    code2llm ./ -f analysis -o ./project
    # Sprawdź czy nie ma nowych god-modules
    if grep -q "GOD" project/analysis.toon; then
        echo "WARNING: God modules detected"
        grep "GOD" project/analysis.toon
    fi
    # Sprawdź CC
    HIGH_CC=$(grep -c "CC.*limit:" project/analysis.toon || true)
    echo "High CC functions: $HIGH_CC"
fi

echo "=== 5. Security check ==="
# Sprawdź czy .env nie jest w git
if git ls-files --error-unmatch .env 2>/dev/null; then
    echo "FAIL: .env is tracked by git!"
    exit 1
fi

echo "=== ALL PASSED ==="
```

### 5.3 GitHub Actions workflow

```yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: '3.12' }
      - run: pip install -e ".[dev]" --break-system-packages
      - run: ruff check src/ tests/
      - run: ruff format --check src/ tests/
      - run: |
          pytest tests/ -x -q --ignore=tests/gui/ \
            --ignore=tests/test_dashboard_live.py \
            --ignore=tests/test_dashboard_performance.py \
            -k "not Live" \
            --tb=short --junitxml=results.xml

  e2e:
    runs-on: ubuntu-latest
    needs: lint-and-test
    services:
      redis: { image: redis:7, ports: ['6379:6379'] }
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: '3.12' }
      - run: pip install -e ".[dev]" --break-system-packages
      - run: pytest tests/test_e2e.py tests/test_project_e2e.py -x -v
```

---

## 6. Podsumowanie — co zrobić w jakiej kolejności

1. ~~**Teraz (5 min):** Dodaj `.toonignore` — wyczyść metryki~~ (pending)
2. ~~**Dziś:** Dodaj endpoint `GET /dashboard/projects`~~ ✅ DONE — ProjectRegistry + API
3. ~~**Dziś:** Dodaj `test_tool_health.py`~~ ✅ DONE — tests/test_tool_health.py
4. ~~**Ten tydzień:** Rozbij `dashboard/__init__.py` god-module~~ ✅ DONE — 35L, sub-moduły
5. ~~**Ten tydzień:** Refaktor `list_tickets` i `chat_completions` (CC=17)~~ ✅ DONE — both extracted into helper functions
6. ~~**Następny sprint:** Adaptery narzędzi~~ ✅ DONE — AiderAdapter, ClaudeCodeAdapter, LLM fallback, ToolExecutor
7. ~~**Następny sprint:** CI pipeline~~ ✅ DONE — .github/workflows/proxym-ci.yml
8. **NEW:** SQLite persistence verified — all managers share single SQLite file, `PROXYM_DATA_DIR` env var, Docker volume persistence, 21 automated tests
9. ~~**Ticket deduplication**~~ ✅ DONE — dodano `proxym ticket dedup [--execute]` + endpoint `POST /dashboard/tickets/dedup` + logika `TicketManager.find_duplicates()`. Usunięto 78 duplikatów z bazy.
