# Taskfile dla agentic-codemod-pipeline (https://taskfile.dev) # # Instalacja go-task: # macOS: brew install go-task # pip: pip install go-task-bin # linux: sh -c "$(curl -sSL https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin # # Wszystkie zadania pracują w lokalnym .venv. Nic nie jest instalowane do systemowego # Pythona - na macOS z Homebrew kończyłoby się to błędem "externally-managed-environment" # (PEP 668), a w najlepszym razie zaśmieceniem interpretera systemowego. version: "3" silent: true # Lokalna konfiguracja developerska. Plik jest opcjonalny i nie trafia do repozytorium # (.gitignore). Zmienne wyeksportowane w powłoce mają pierwszeństwo przed .env, # więc `eval "$(task llm:env)"` zawsze wygrywa z tym, co tu wpisano. dotenv: [".env"] vars: VENV: .venv BIN: "{{.VENV}}/bin" PY: "{{.VENV}}/bin/python" CLI: "{{.VENV}}/bin/agentic-codemod" FIXTURE: examples/fixtures/acme-app # Interpreter bazowy do utworzenia venva. Na macOS z systemowym Pythonem 3.9: # PYTHON=python3.12 task install PYTHON: '{{.PYTHON | default "python3"}}' # Backend LLM: auto (wykrycie platformy) | vllm-gpu | vllm-metal | ollama | mock LLM_BACKEND: '{{.LLM_BACKEND | default "auto"}}' LLM_RUN_DIR: .runs/llm COMPOSE: docker compose -f llm/docker-compose.yml BACKEND_PY: "{{.VENV}}/bin/python llm/scripts/backend.py" env: PYTHONDONTWRITEBYTECODE: "1" tasks: default: desc: Lista dostępnych zadań cmds: - task --list # ------------------------------------------------------------- środowisko doctor: desc: Diagnostyka środowiska - uruchom to najpierw, gdy coś nie działa cmds: - | printf 'system : %s %s\n' "$(uname -s)" "$(uname -m)" printf 'interpreter bazowy: %s -> %s\n' "{{.PYTHON}}" "$({{.PYTHON}} -V 2>&1 || echo BRAK)" printf 'venv : %s\n' "$(test -x {{.PY}} && {{.PY}} -V 2>&1 || echo 'brak (uruchom: task install)')" printf 'uv : %s\n' "$(command -v uv >/dev/null 2>&1 && uv --version || echo 'brak (opcjonalny, przyspiesza instalację)')" printf 'git : %s\n' "$(git --version 2>/dev/null || echo BRAK)" printf 'ripgrep : %s\n' "$(rg --version 2>/dev/null | head -1 || echo 'brak (opcjonalny, używany przez skille)')" printf 'apm : %s\n' "$(apm --version 2>/dev/null || echo 'brak (potrzebny do apm install)')" printf 'agno : %s\n' "$(test -x {{.PY}} && {{.PY}} -c 'import agno; print(agno.__version__)' 2>/dev/null || echo 'brak')" venv: internal: true preconditions: - sh: command -v {{.PYTHON}} >/dev/null 2>&1 msg: | Nie znaleziono interpretera "{{.PYTHON}}". Na macOS: brew install python@3.12, a potem PYTHON=python3.12 task install - sh: '{{.PYTHON}} -c "import sys; sys.exit(0 if sys.version_info >= (3, 10) else 1)"' msg: | Wymagany Python 3.10 lub nowszy ({{.PYTHON}} jest starszy). macOS ma w systemie 3.9 - zainstaluj nowszy i wskaż go: brew install python@3.12 && PYTHON=python3.12 task install status: - test -x {{.PY}} cmds: - | # Niekompletny venv (przerwana instalacja, skasowany interpreter) jest gorszy # niż jego brak - narzędzia zgłaszają wtedy mylące błędy. Odtwarzamy go od zera. if [ -d "{{.VENV}}" ] && [ ! -x "{{.PY}}" ]; then echo "usuwam niekompletny {{.VENV}}" rm -rf "{{.VENV}}" fi if command -v uv >/dev/null 2>&1; then uv venv --python {{.PYTHON}} {{.VENV}} else {{.PYTHON}} -m venv {{.VENV}} fi install: desc: Tworzy .venv i instaluje zależności (runtime + dev) deps: [venv] status: - test -f {{.VENV}}/.install-stamp - test {{.VENV}}/.install-stamp -nt pyproject.toml cmds: - | if command -v uv >/dev/null 2>&1; then uv pip install --python {{.PY}} -e ".[dev]" else {{.PY}} -m pip install --quiet --upgrade pip {{.PY}} -m pip install -e ".[dev]" fi - touch {{.VENV}}/.install-stamp - 'echo "gotowe: {{.CLI}}"' context: desc: Instaluje i audytuje kontekst agentowy (APM), potem go wypisuje deps: [install] preconditions: - sh: command -v apm >/dev/null 2>&1 msg: | Brak APM CLI. Instalacja: macOS: brew install apm (albo pip install apm-cli) inne: curl -sSL https://aka.ms/apm-unix | sh Repozytorium działa też bez APM CLI - wtedy używany jest wyłącznie lokalny kontekst z .apm/ (bez zależności zewnętrznych). cmds: - apm install - apm audit - "{{.CLI}} context" context:show: desc: Wypisuje kontekst agentowy widziany przez silnik (bez apm install) deps: [install] cmds: - "{{.CLI}} context" # ------------------------------------------------------------------ jakość lint: desc: Statyczna analiza i sprawdzenie formatowania deps: [install] cmds: - "{{.BIN}}/ruff check src tests llm" - "{{.BIN}}/ruff format --check src tests llm" fmt: desc: Automatyczna poprawa formatowania i prostych błędów deps: [install] cmds: - "{{.BIN}}/ruff check --fix src tests llm" - "{{.BIN}}/ruff format src tests llm" test: desc: Testy jednostkowe i integracyjne (bez modelu językowego) deps: [install] cmds: - "{{.BIN}}/pytest -q {{.CLI_ARGS}}" test:cov: desc: Testy z raportem pokrycia deps: [install] cmds: - "{{.BIN}}/pytest -q --cov=agentic_codemod --cov-report=term-missing" ci: desc: To, co sprawdza pipeline - uruchom przed pushem cmds: - task: lint - task: test - task: demo # ------------------------------------------------------------- uruchomienia demo: desc: Pełny przebieg offline na fixture (bez LLM, bez sieci) deps: [install] vars: RUN_DIR: '{{.RUN_DIR | default ".runs/demo"}}' cmds: - | {{.CLI}} run \ --repo {{.FIXTURE}} \ --package acme-sdk --module acme --to-version 2.1.0 \ --offline --run-dir "{{.RUN_DIR}}" - 'echo "diff: {{.RUN_DIR}}/changes.patch | manifest: {{.RUN_DIR}}/run.json"' plan: desc: 'Sam plan zmian, bez modyfikacji kodu. Użycie: task plan -- --repo ../repo --package acme-sdk --to-version 2.1.0' deps: [install] cmds: - "{{.CLI}} run --plan-only --run-dir .runs/plan {{.CLI_ARGS}}" run: desc: 'Przebieg z modelem. Użycie: task run -- --repo ../repo --package acme-sdk --to-version 2.1.0' deps: [install] preconditions: - sh: test -n "$CODEMOD_LLM_BASE_URL" msg: | Nie ustawiono CODEMOD_LLM_BASE_URL. Skopiuj .env.example do .env i wyeksportuj zmienne, albo uruchom przebieg deterministyczny: task demo cmds: - "{{.CLI}} run --run-dir .runs/local {{.CLI_ARGS}}" # ------------------------------------------------------------------- LLM # Kontrakt jest jeden: endpoint zgodny z OpenAI pod CODEMOD_LLM_BASE_URL. # Backendy różnią się wyłącznie tym, jak ten endpoint powstaje. # Konfiguracja: llm/models.yml. Wybór ręczny: LLM_BACKEND=ollama task llm:up llm:list: desc: Dostępne backendy LLM i który pasuje do tej maszyny deps: [install] cmds: - "{{.BACKEND_PY}} list" llm:env: desc: 'Zmienne środowiskowe dla wybranego backendu. Użycie: eval "$(task llm:env)"' deps: [install] cmds: - "{{.BACKEND_PY}} env --backend {{.LLM_BACKEND}}" llm:up: desc: Uruchamia backend LLM (autodetekcja platformy) i czeka na gotowość endpointu deps: [install] cmds: - mkdir -p {{.LLM_RUN_DIR}} - | set -e BACKEND="$({{.BACKEND_PY}} detect --requested {{.LLM_BACKEND}})" MODEL="$({{.BACKEND_PY}} get serve.model --backend "$BACKEND")" BASE_URL="$({{.BACKEND_PY}} get base_url --backend "$BACKEND")" echo "backend: $BACKEND | model: $MODEL | endpoint: $BASE_URL" case "$BACKEND" in vllm-gpu) command -v docker >/dev/null || { echo "brak dockera - zainstaluj albo: LLM_BACKEND=ollama task llm:up" >&2; exit 1; } LLM_SERVE_MODEL="$MODEL" {{.COMPOSE}} --profile vllm-gpu up -d LOG="" ;; vllm-metal) LOG="{{.LLM_RUN_DIR}}/vllm-metal.log" llm/scripts/vllm-metal.sh up "$MODEL" 8000 "$LOG" "{{.LLM_RUN_DIR}}/vllm-metal.pid" ;; ollama) if command -v ollama >/dev/null 2>&1; then # macOS: natywna Ollama korzysta z Metala; w kontenerze liczyłaby na CPU curl -fsS --max-time 3 http://localhost:11434/api/version >/dev/null 2>&1 || { {{.PY}} llm/scripts/daemonize.py --pidfile "{{.LLM_RUN_DIR}}/ollama.pid" \ --log "{{.LLM_RUN_DIR}}/ollama.log" -- ollama serve sleep 2 } ollama pull "$MODEL" else {{.COMPOSE}} --profile ollama up -d {{.COMPOSE}} exec -T ollama ollama pull "$MODEL" fi LOG="{{.LLM_RUN_DIR}}/ollama.log" ;; mock) LOG="{{.LLM_RUN_DIR}}/mock.log" {{.PY}} llm/scripts/daemonize.py --pidfile "{{.LLM_RUN_DIR}}/mock.pid" --log "$LOG" -- \ {{.PY}} llm/mock_server.py --port 8077 ;; *) echo "nieznany backend: $BACKEND" >&2; exit 1 ;; esac echo "podgląd logów w drugim oknie: task llm:logs" llm/scripts/wait-for-endpoint.sh "$BASE_URL" "${LLM_WAIT_TIMEOUT:-900}" "$LOG" echo echo "gotowe. Wyeksportuj zmienne do swojej powłoki:" echo ' eval "$(task llm:env)"' llm:down: desc: Zatrzymuje backend LLM deps: [install] cmds: - | BACKEND="$({{.BACKEND_PY}} detect --requested {{.LLM_BACKEND}})" case "$BACKEND" in vllm-gpu) {{.COMPOSE}} --profile vllm-gpu down ;; ollama) if [ -f "{{.LLM_RUN_DIR}}/ollama.pid" ]; then {{.PY}} llm/scripts/daemonize.py --pidfile "{{.LLM_RUN_DIR}}/ollama.pid" --log /dev/null --stop else {{.COMPOSE}} --profile ollama down fi ;; vllm-metal) llm/scripts/vllm-metal.sh down "{{.LLM_RUN_DIR}}/vllm-metal.pid" ;; mock) {{.PY}} llm/scripts/daemonize.py --pidfile "{{.LLM_RUN_DIR}}/mock.pid" --log /dev/null --stop ;; esac llm:status: desc: Czy endpoint LLM odpowiada i jakie modele wystawia deps: [install] cmds: - | BASE_URL="$({{.BACKEND_PY}} get base_url --backend {{.LLM_BACKEND}})" echo "endpoint: $BASE_URL" curl -fsS --max-time 5 "$BASE_URL/models" || { echo "brak odpowiedzi - uruchom: task llm:up" >&2; exit 1; } llm:smoke: desc: Jedno zapytanie do endpointu - sprawdza, czy model w ogóle odpowiada deps: [install] cmds: - | BASE_URL="$({{.BACKEND_PY}} get base_url --backend {{.LLM_BACKEND}})" MODEL="$({{.BACKEND_PY}} get serve.model --backend {{.LLM_BACKEND}})" curl -fsS --max-time 120 "$BASE_URL/chat/completions" \ -H 'Content-Type: application/json' \ -d "{\"model\":\"$MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"Odpowiedz jednym slowem: dziala\"}],\"max_tokens\":16}" echo llm:logs: desc: 'Logi backendu LLM na żywo. Warianty: TAIL=500, FOLLOW=false, GREP=wzorzec' deps: [install] vars: TAIL: '{{.TAIL | default "100"}}' FOLLOW: '{{.FOLLOW | default "true"}}' GREP: '{{.GREP | default ""}}' cmds: - | BACKEND="$({{.BACKEND_PY}} detect --requested {{.LLM_BACKEND}})" llm/scripts/logs.sh \ --backend "$BACKEND" \ --run-dir "{{.LLM_RUN_DIR}}" \ --tail "{{.TAIL}}" \ {{if eq .FOLLOW "false"}}--no-follow{{else}}--follow{{end}} \ {{if .GREP}}--grep "{{.GREP}}"{{end}} llm:logs:errors: desc: Same błędy i ostrzeżenia z logów backendu (jednorazowy zrzut) deps: [install] vars: TAIL: '{{.TAIL | default "2000"}}' cmds: - | BACKEND="$({{.BACKEND_PY}} detect --requested {{.LLM_BACKEND}})" llm/scripts/logs.sh --backend "$BACKEND" --run-dir "{{.LLM_RUN_DIR}}" \ --tail "{{.TAIL}}" --no-follow \ --grep "error|warning|traceback|exception|failed|out of memory|cuda|refused" llm:logs:save: desc: 'Zrzut logów do pliku, do dołączenia w zgłoszeniu. Domyślnie .runs/llm/dump.log' deps: [install] vars: TAIL: '{{.TAIL | default "5000"}}' OUT: '{{.OUT | default ".runs/llm/dump.log"}}' cmds: - | BACKEND="$({{.BACKEND_PY}} detect --requested {{.LLM_BACKEND}})" llm/scripts/logs.sh --backend "$BACKEND" --run-dir "{{.LLM_RUN_DIR}}" \ --tail "{{.TAIL}}" --save "{{.OUT}}" llm:mock: desc: Atrapa serwera OpenAI na pierwszym planie (Ctrl-C kończy) deps: [install] cmds: - "{{.PY}} llm/mock_server.py --port 8077 --verbose" # ------------------------------------------------------- przebiegi z LLM demo:llm: desc: Przebieg agentowy na fixture z aktualnym backendem LLM (domyślnie atrapa - deterministyczny) deps: [install] vars: RUN_DIR: '{{.RUN_DIR | default ".runs/demo-llm"}}' cmds: - task: llm:up vars: {LLM_BACKEND: "{{.LLM_BACKEND}}"} - | eval "$({{.BACKEND_PY}} env --backend {{.LLM_BACKEND}})" {{.CLI}} run \ --repo {{.FIXTURE}} \ --package acme-sdk --module acme --to-version 2.1.0 \ --run-dir "{{.RUN_DIR}}" test:llm: desc: Testy ścieżki agentowej na atrapie LLM (bez GPU i bez sieci) deps: [install] cmds: - "{{.BIN}}/pytest -q tests/test_llm_path.py" # -------------------------------------------------------------- sprzątanie clean: desc: Usuwa artefakty przebiegów i cache narzędzi cmds: - rm -rf .runs workspace .pytest_cache .ruff_cache .coverage htmlcov - find . -type d -name __pycache__ -prune -exec rm -rf {} + distclean: desc: Jak clean, plus .venv i zainstalowany kontekst APM deps: [clean] cmds: - rm -rf {{.VENV}} apm_modules *.egg-info src/*.egg-info