J.A.R.V.I.S-py/dev_handlers.py

314 lines
11 KiB
Python
Raw Normal View History

feat: codebase_qa + github_pr in Python (142 → 148 commands) Closes the Python parity gap with rust. All major imba features now mirrored EXCEPT live LLM hot-swap (python LLM backend is still env-driven, not voice- switchable like rust). dev_handlers.py (new, ~250 lines) Codebase Q&A: do_codebase_set "укажи проект C:\path" → memory_store.remember("codebase.root", ...) do_codebase_where "какой проект сейчас" do_codebase_ask "что делает функция X" / "найди в коде Y" Walks the root with same caps as rust: depth=3, files=30, file_bytes=4096, total=50000 Filters by extension (rs/py/ts/lua/go/...). Skips .git/target/node_modules/__pycache__/.venv etc. Feeds digest to Groq LLM with "senior reviewer" prompt. GitHub PR review (requires `gh` CLI authenticated): do_github_set_repo "текущий репо owner/repo" → memory_store do_github_list_prs "какие пиары" → `gh pr list --json number,title,author`, speaks count + first 3 titles do_github_summarize_pr "разбери последний пиар" → gh pr view of latest open PR, feeds title+body+diff stat to LLM, speaks 3-5 sentence review. extensions.py — 6 new proxy handlers + import dev_handlers + set_speak wires it. main.py — 6 new action_type dispatches in run_action. commands.yaml (+6 entries, 142 → 148): codebase_set, codebase_where, codebase_ask, github_set_repo, github_list_prs, github_summarize_pr. Tests: ast.parse passes for all .py files. yaml.safe_load = 148 entries. PYTHON PARITY VS RUST IS NOW COMPLETE except: ✗ LLM hot-swap voice command (rust calls llm::swap_to + persists DB; python config.GROQ_TOKEN is read once at startup) ✗ GUI pages (rust has Tauri+Svelte, python is console-only) Everything else has full parity: memory / profiles / vision / macros / scheduler / codebase Q&A / GitHub PR review / 14 utility packs.
2026-05-16 00:40:08 +03:00
"""Developer-oriented handlers — port of `codebase_qa/` and `github_pr/` packs.
Codebase Q&A: point Jarvis at a folder, ask questions; he walks source files
(depth-limited, size-capped) and feeds a digest to the LLM.
GitHub PR review: requires the `gh` CLI authenticated. Lists open PRs or
reviews the latest one via LLM.
Both need GROQ_TOKEN (or a configured OpenAI-compat endpoint).
"""
import json
import os
import re
import subprocess
import memory_store
feat: LLM hot-swap voice command + Ollama backend (148 → 151 commands) Closes the last functional parity gap with rust. Python now has voice-driven Ollama↔Groq switching, persistent across restarts. llm_backend.py (new, ~130 lines) Singleton module: current_client() → active OpenAI client (or None) current_backend() → 'groq' | 'ollama' | 'none' current_model() → active model name swap_to(name) → hot-swap + persist to <here>/llm_backend.txt parse_backend(name) → ru/en alias normaliser ('облако'→groq, 'локальный'→ollama) Backends: - Groq: config.GROQ_TOKEN + GROQ_BASE_URL + GROQ_MODEL (defaults) - Ollama: OLLAMA_BASE_URL (default http://localhost:11434/v1) + OLLAMA_MODEL (default qwen2.5:3b). api_key='ollama' (placeholder, openai lib insists on a non-empty string; Ollama ignores it). Init precedence (idempotent _ensure_init): 1. Persisted choice from llm_backend.txt 2. JARVIS_LLM env var 3. Auto-detect: Groq if token present, else Ollama Voice commands (commands.yaml, +3 entries → 151) llm_switch_local → ollama llm_switch_cloud → groq llm_status → speaks current backend extensions.py + do_llm_switch / do_llm_status handlers + llm_backend import do_interesting_fact migrated off direct OpenAI(...) to use llm_backend.current_* dev_handlers.py + llm_backend import do_codebase_ask + do_github_summarize_pr migrated to llm_backend.current_* vision_handler.py Vision call documented as Groq-specific (Ollama doesn't expose vision via OpenAI-compat in our stack), kept direct config.GROQ_TOKEN reading. Tests: ast.parse passes for all 5 modules. yaml.safe_load = 151 entries. PYTHON PARITY VS RUST IS NOW FUNCTIONALLY COMPLETE. Only remaining rust-only feature: the Tauri GUI (python is console-only).
2026-05-16 00:44:42 +03:00
import llm_backend
feat: codebase_qa + github_pr in Python (142 → 148 commands) Closes the Python parity gap with rust. All major imba features now mirrored EXCEPT live LLM hot-swap (python LLM backend is still env-driven, not voice- switchable like rust). dev_handlers.py (new, ~250 lines) Codebase Q&A: do_codebase_set "укажи проект C:\path" → memory_store.remember("codebase.root", ...) do_codebase_where "какой проект сейчас" do_codebase_ask "что делает функция X" / "найди в коде Y" Walks the root with same caps as rust: depth=3, files=30, file_bytes=4096, total=50000 Filters by extension (rs/py/ts/lua/go/...). Skips .git/target/node_modules/__pycache__/.venv etc. Feeds digest to Groq LLM with "senior reviewer" prompt. GitHub PR review (requires `gh` CLI authenticated): do_github_set_repo "текущий репо owner/repo" → memory_store do_github_list_prs "какие пиары" → `gh pr list --json number,title,author`, speaks count + first 3 titles do_github_summarize_pr "разбери последний пиар" → gh pr view of latest open PR, feeds title+body+diff stat to LLM, speaks 3-5 sentence review. extensions.py — 6 new proxy handlers + import dev_handlers + set_speak wires it. main.py — 6 new action_type dispatches in run_action. commands.yaml (+6 entries, 142 → 148): codebase_set, codebase_where, codebase_ask, github_set_repo, github_list_prs, github_summarize_pr. Tests: ast.parse passes for all .py files. yaml.safe_load = 148 entries. PYTHON PARITY VS RUST IS NOW COMPLETE except: ✗ LLM hot-swap voice command (rust calls llm::swap_to + persists DB; python config.GROQ_TOKEN is read once at startup) ✗ GUI pages (rust has Tauri+Svelte, python is console-only) Everything else has full parity: memory / profiles / vision / macros / scheduler / codebase Q&A / GitHub PR review / 14 utility packs.
2026-05-16 00:40:08 +03:00
_speak_fn = print
def set_speak(fn):
global _speak_fn
_speak_fn = fn
def _speak(text):
try:
_speak_fn(text)
except Exception as exc:
print(f"[dev] speak: {exc}")
# ── codebase Q&A ────────────────────────────────────────────────────────────
_EXT_OK = {
'rs', 'lua', 'py', 'ts', 'tsx', 'js', 'jsx', 'svelte',
'go', 'java', 'kt', 'c', 'h', 'cpp', 'hpp', 'cs',
'rb', 'php', 'sh', 'ps1', 'sql', 'toml', 'yaml', 'yml',
'json', 'md',
}
_SKIP_DIR = {
'.git', 'target', 'node_modules', 'dist', 'build',
'__pycache__', '.venv', 'venv', '.idea', '.vscode',
}
_MAX_FILE_BYTES = 4096
_MAX_TOTAL_BYTES = 50_000
_MAX_FILES = 30
_MAX_DEPTH = 3
def _walk_codebase(root: str) -> list[tuple[str, str]]:
"""Return list of (relative_path, content) tuples — capped per the constants above."""
chunks = []
total_bytes = 0
root = os.path.normpath(root)
if not os.path.isdir(root):
return chunks
for dirpath, dirnames, filenames in os.walk(root):
# Depth gate
rel = os.path.relpath(dirpath, root)
depth = 0 if rel == '.' else rel.count(os.sep) + 1
if depth > _MAX_DEPTH:
dirnames[:] = []
continue
# Prune skip dirs in-place
dirnames[:] = [d for d in dirnames
if d not in _SKIP_DIR and not d.startswith('.')]
for fname in filenames:
if len(chunks) >= _MAX_FILES or total_bytes >= _MAX_TOTAL_BYTES:
return chunks
_, dot_ext = os.path.splitext(fname)
ext = (dot_ext or '').lstrip('.').lower()
if ext not in _EXT_OK:
continue
fp = os.path.join(dirpath, fname)
try:
with open(fp, 'r', encoding='utf-8', errors='replace') as fh:
content = fh.read(_MAX_FILE_BYTES + 1)
except (OSError, ValueError):
continue
if len(content) > _MAX_FILE_BYTES:
content = content[:_MAX_FILE_BYTES] + "\n... [truncated]"
rel_path = os.path.relpath(fp, root)
chunks.append((rel_path, content))
total_bytes += len(content)
return chunks
def do_codebase_set(action, voice):
"""'Укажи проект C:\\Jarvis\\rust' — stores root via memory."""
body = (voice or '').strip()
low = body.lower()
for trig in ('укажи папку проекта', 'укажи кодовую базу',
'укажи проект', 'выбери проект', 'проект сейчас'):
if low.startswith(trig):
body = body[len(trig):].strip(' ,.:')
break
if not body:
_speak("Укажите путь к папке проекта.")
return
if not os.path.isdir(body):
_speak(f"Папка не найдена: {body}.")
return
memory_store.remember("codebase.root", body)
_speak("Проект установлен.")
def do_codebase_where(action, voice):
root = memory_store.recall("codebase.root")
if not root:
_speak("Проект не выбран. Скажите: укажи проект и путь.")
return
_speak(f"Сейчас работаю с проектом {root}.")
def do_codebase_ask(action, voice):
"""'Что делает функция X' / 'найди в коде Y'."""
root = memory_store.recall("codebase.root")
if not root:
_speak("Сначала укажите проект.")
return
body = (voice or '').strip()
low = body.lower()
for trig in ('что делает функция', 'вопрос по коду',
'спроси код', 'найди в проекте',
'найди в коде', 'что в коде', 'объясни код'):
idx = low.find(trig)
if idx >= 0:
body = body[idx + len(trig):].strip(' ,.:')
break
if not body:
_speak("Сформулируйте вопрос.")
return
chunks = _walk_codebase(root)
if not chunks:
_speak("Не нашёл исходников в проекте.")
return
digest = "\n\n".join(f"--- {p} ---\n{c}" for p, c in chunks)
feat: LLM hot-swap voice command + Ollama backend (148 → 151 commands) Closes the last functional parity gap with rust. Python now has voice-driven Ollama↔Groq switching, persistent across restarts. llm_backend.py (new, ~130 lines) Singleton module: current_client() → active OpenAI client (or None) current_backend() → 'groq' | 'ollama' | 'none' current_model() → active model name swap_to(name) → hot-swap + persist to <here>/llm_backend.txt parse_backend(name) → ru/en alias normaliser ('облако'→groq, 'локальный'→ollama) Backends: - Groq: config.GROQ_TOKEN + GROQ_BASE_URL + GROQ_MODEL (defaults) - Ollama: OLLAMA_BASE_URL (default http://localhost:11434/v1) + OLLAMA_MODEL (default qwen2.5:3b). api_key='ollama' (placeholder, openai lib insists on a non-empty string; Ollama ignores it). Init precedence (idempotent _ensure_init): 1. Persisted choice from llm_backend.txt 2. JARVIS_LLM env var 3. Auto-detect: Groq if token present, else Ollama Voice commands (commands.yaml, +3 entries → 151) llm_switch_local → ollama llm_switch_cloud → groq llm_status → speaks current backend extensions.py + do_llm_switch / do_llm_status handlers + llm_backend import do_interesting_fact migrated off direct OpenAI(...) to use llm_backend.current_* dev_handlers.py + llm_backend import do_codebase_ask + do_github_summarize_pr migrated to llm_backend.current_* vision_handler.py Vision call documented as Groq-specific (Ollama doesn't expose vision via OpenAI-compat in our stack), kept direct config.GROQ_TOKEN reading. Tests: ast.parse passes for all 5 modules. yaml.safe_load = 151 entries. PYTHON PARITY VS RUST IS NOW FUNCTIONALLY COMPLETE. Only remaining rust-only feature: the Tauri GUI (python is console-only).
2026-05-16 00:44:42 +03:00
client = llm_backend.current_client()
if client is None:
_speak("LLM не настроен.")
feat: codebase_qa + github_pr in Python (142 → 148 commands) Closes the Python parity gap with rust. All major imba features now mirrored EXCEPT live LLM hot-swap (python LLM backend is still env-driven, not voice- switchable like rust). dev_handlers.py (new, ~250 lines) Codebase Q&A: do_codebase_set "укажи проект C:\path" → memory_store.remember("codebase.root", ...) do_codebase_where "какой проект сейчас" do_codebase_ask "что делает функция X" / "найди в коде Y" Walks the root with same caps as rust: depth=3, files=30, file_bytes=4096, total=50000 Filters by extension (rs/py/ts/lua/go/...). Skips .git/target/node_modules/__pycache__/.venv etc. Feeds digest to Groq LLM with "senior reviewer" prompt. GitHub PR review (requires `gh` CLI authenticated): do_github_set_repo "текущий репо owner/repo" → memory_store do_github_list_prs "какие пиары" → `gh pr list --json number,title,author`, speaks count + first 3 titles do_github_summarize_pr "разбери последний пиар" → gh pr view of latest open PR, feeds title+body+diff stat to LLM, speaks 3-5 sentence review. extensions.py — 6 new proxy handlers + import dev_handlers + set_speak wires it. main.py — 6 new action_type dispatches in run_action. commands.yaml (+6 entries, 142 → 148): codebase_set, codebase_where, codebase_ask, github_set_repo, github_list_prs, github_summarize_pr. Tests: ast.parse passes for all .py files. yaml.safe_load = 148 entries. PYTHON PARITY VS RUST IS NOW COMPLETE except: ✗ LLM hot-swap voice command (rust calls llm::swap_to + persists DB; python config.GROQ_TOKEN is read once at startup) ✗ GUI pages (rust has Tauri+Svelte, python is console-only) Everything else has full parity: memory / profiles / vision / macros / scheduler / codebase Q&A / GitHub PR review / 14 utility packs.
2026-05-16 00:40:08 +03:00
return
feat: LLM hot-swap voice command + Ollama backend (148 → 151 commands) Closes the last functional parity gap with rust. Python now has voice-driven Ollama↔Groq switching, persistent across restarts. llm_backend.py (new, ~130 lines) Singleton module: current_client() → active OpenAI client (or None) current_backend() → 'groq' | 'ollama' | 'none' current_model() → active model name swap_to(name) → hot-swap + persist to <here>/llm_backend.txt parse_backend(name) → ru/en alias normaliser ('облако'→groq, 'локальный'→ollama) Backends: - Groq: config.GROQ_TOKEN + GROQ_BASE_URL + GROQ_MODEL (defaults) - Ollama: OLLAMA_BASE_URL (default http://localhost:11434/v1) + OLLAMA_MODEL (default qwen2.5:3b). api_key='ollama' (placeholder, openai lib insists on a non-empty string; Ollama ignores it). Init precedence (idempotent _ensure_init): 1. Persisted choice from llm_backend.txt 2. JARVIS_LLM env var 3. Auto-detect: Groq if token present, else Ollama Voice commands (commands.yaml, +3 entries → 151) llm_switch_local → ollama llm_switch_cloud → groq llm_status → speaks current backend extensions.py + do_llm_switch / do_llm_status handlers + llm_backend import do_interesting_fact migrated off direct OpenAI(...) to use llm_backend.current_* dev_handlers.py + llm_backend import do_codebase_ask + do_github_summarize_pr migrated to llm_backend.current_* vision_handler.py Vision call documented as Groq-specific (Ollama doesn't expose vision via OpenAI-compat in our stack), kept direct config.GROQ_TOKEN reading. Tests: ast.parse passes for all 5 modules. yaml.safe_load = 151 entries. PYTHON PARITY VS RUST IS NOW FUNCTIONALLY COMPLETE. Only remaining rust-only feature: the Tauri GUI (python is console-only).
2026-05-16 00:44:42 +03:00
model = llm_backend.current_model()
feat: codebase_qa + github_pr in Python (142 → 148 commands) Closes the Python parity gap with rust. All major imba features now mirrored EXCEPT live LLM hot-swap (python LLM backend is still env-driven, not voice- switchable like rust). dev_handlers.py (new, ~250 lines) Codebase Q&A: do_codebase_set "укажи проект C:\path" → memory_store.remember("codebase.root", ...) do_codebase_where "какой проект сейчас" do_codebase_ask "что делает функция X" / "найди в коде Y" Walks the root with same caps as rust: depth=3, files=30, file_bytes=4096, total=50000 Filters by extension (rs/py/ts/lua/go/...). Skips .git/target/node_modules/__pycache__/.venv etc. Feeds digest to Groq LLM with "senior reviewer" prompt. GitHub PR review (requires `gh` CLI authenticated): do_github_set_repo "текущий репо owner/repo" → memory_store do_github_list_prs "какие пиары" → `gh pr list --json number,title,author`, speaks count + first 3 titles do_github_summarize_pr "разбери последний пиар" → gh pr view of latest open PR, feeds title+body+diff stat to LLM, speaks 3-5 sentence review. extensions.py — 6 new proxy handlers + import dev_handlers + set_speak wires it. main.py — 6 new action_type dispatches in run_action. commands.yaml (+6 entries, 142 → 148): codebase_set, codebase_where, codebase_ask, github_set_repo, github_list_prs, github_summarize_pr. Tests: ast.parse passes for all .py files. yaml.safe_load = 148 entries. PYTHON PARITY VS RUST IS NOW COMPLETE except: ✗ LLM hot-swap voice command (rust calls llm::swap_to + persists DB; python config.GROQ_TOKEN is read once at startup) ✗ GUI pages (rust has Tauri+Svelte, python is console-only) Everything else has full parity: memory / profiles / vision / macros / scheduler / codebase Q&A / GitHub PR review / 14 utility packs.
2026-05-16 00:40:08 +03:00
user_prompt = (
f"Ты — старший разработчик. По digest проекта ответь на вопрос пользователя "
f"кратко (3-5 предложений) на русском. Указывай файлы где уместно.\n\n"
f"=== ВОПРОС ===\n{body}\n\n=== КОД ===\n{digest}"
)
try:
resp = client.chat.completions.create(
feat: LLM hot-swap voice command + Ollama backend (148 → 151 commands) Closes the last functional parity gap with rust. Python now has voice-driven Ollama↔Groq switching, persistent across restarts. llm_backend.py (new, ~130 lines) Singleton module: current_client() → active OpenAI client (or None) current_backend() → 'groq' | 'ollama' | 'none' current_model() → active model name swap_to(name) → hot-swap + persist to <here>/llm_backend.txt parse_backend(name) → ru/en alias normaliser ('облако'→groq, 'локальный'→ollama) Backends: - Groq: config.GROQ_TOKEN + GROQ_BASE_URL + GROQ_MODEL (defaults) - Ollama: OLLAMA_BASE_URL (default http://localhost:11434/v1) + OLLAMA_MODEL (default qwen2.5:3b). api_key='ollama' (placeholder, openai lib insists on a non-empty string; Ollama ignores it). Init precedence (idempotent _ensure_init): 1. Persisted choice from llm_backend.txt 2. JARVIS_LLM env var 3. Auto-detect: Groq if token present, else Ollama Voice commands (commands.yaml, +3 entries → 151) llm_switch_local → ollama llm_switch_cloud → groq llm_status → speaks current backend extensions.py + do_llm_switch / do_llm_status handlers + llm_backend import do_interesting_fact migrated off direct OpenAI(...) to use llm_backend.current_* dev_handlers.py + llm_backend import do_codebase_ask + do_github_summarize_pr migrated to llm_backend.current_* vision_handler.py Vision call documented as Groq-specific (Ollama doesn't expose vision via OpenAI-compat in our stack), kept direct config.GROQ_TOKEN reading. Tests: ast.parse passes for all 5 modules. yaml.safe_load = 151 entries. PYTHON PARITY VS RUST IS NOW FUNCTIONALLY COMPLETE. Only remaining rust-only feature: the Tauri GUI (python is console-only).
2026-05-16 00:44:42 +03:00
model=model,
feat: codebase_qa + github_pr in Python (142 → 148 commands) Closes the Python parity gap with rust. All major imba features now mirrored EXCEPT live LLM hot-swap (python LLM backend is still env-driven, not voice- switchable like rust). dev_handlers.py (new, ~250 lines) Codebase Q&A: do_codebase_set "укажи проект C:\path" → memory_store.remember("codebase.root", ...) do_codebase_where "какой проект сейчас" do_codebase_ask "что делает функция X" / "найди в коде Y" Walks the root with same caps as rust: depth=3, files=30, file_bytes=4096, total=50000 Filters by extension (rs/py/ts/lua/go/...). Skips .git/target/node_modules/__pycache__/.venv etc. Feeds digest to Groq LLM with "senior reviewer" prompt. GitHub PR review (requires `gh` CLI authenticated): do_github_set_repo "текущий репо owner/repo" → memory_store do_github_list_prs "какие пиары" → `gh pr list --json number,title,author`, speaks count + first 3 titles do_github_summarize_pr "разбери последний пиар" → gh pr view of latest open PR, feeds title+body+diff stat to LLM, speaks 3-5 sentence review. extensions.py — 6 new proxy handlers + import dev_handlers + set_speak wires it. main.py — 6 new action_type dispatches in run_action. commands.yaml (+6 entries, 142 → 148): codebase_set, codebase_where, codebase_ask, github_set_repo, github_list_prs, github_summarize_pr. Tests: ast.parse passes for all .py files. yaml.safe_load = 148 entries. PYTHON PARITY VS RUST IS NOW COMPLETE except: ✗ LLM hot-swap voice command (rust calls llm::swap_to + persists DB; python config.GROQ_TOKEN is read once at startup) ✗ GUI pages (rust has Tauri+Svelte, python is console-only) Everything else has full parity: memory / profiles / vision / macros / scheduler / codebase Q&A / GitHub PR review / 14 utility packs.
2026-05-16 00:40:08 +03:00
messages=[
{'role': 'system', 'content': 'Ты — внимательный код-ревьюер. По существу, без воды.'},
{'role': 'user', 'content': user_prompt},
],
max_tokens=400, temperature=0.2, timeout=45,
)
answer = resp.choices[0].message.content.strip()
except Exception as exc:
print(f"[dev] codebase LLM: {exc}")
_speak("Не получилось получить ответ.")
return
if not answer:
_speak("Пустой ответ.")
return
_speak(answer)
# ── github PR ───────────────────────────────────────────────────────────────
def _gh(*args, timeout=15):
"""Run gh CLI, return (success, stdout, stderr)."""
try:
res = subprocess.run(
['gh', *args],
capture_output=True, text=True, encoding='utf-8',
timeout=timeout,
)
return res.returncode == 0, res.stdout, res.stderr
except FileNotFoundError:
return False, '', 'gh CLI not installed'
except Exception as exc:
return False, '', str(exc)
def do_github_set_repo(action, voice):
body = (voice or '').strip()
low = body.lower()
for trig in ('установи репо', 'выбери репо', 'текущий репо', 'репозиторий'):
if low.startswith(trig):
body = body[len(trig):].strip(' ,.:')
break
if not body or '/' not in body:
_speak("Скажите owner slash repo, например bossiara13 slash J A R V I S.")
return
memory_store.remember("github.repo", body)
_speak(f"Репозиторий {body} установлен.")
def do_github_list_prs(action, voice):
repo = memory_store.recall("github.repo")
if not repo:
_speak("Сначала укажите репозиторий: текущий репо owner/repo.")
return
ok, out, err = _gh('pr', 'list', '--repo', repo, '--state', 'open',
'--json', 'number,title,author', '--limit', '10')
if not ok:
print(f"[dev] gh list: {err[:200]}")
_speak("gh CLI не отвечает. Установите gh и выполните gh auth login.")
return
try:
prs = json.loads(out or '[]')
except json.JSONDecodeError:
prs = []
if not prs:
_speak("Открытых пиаров нет.")
return
line = f"{len(prs)} открытых пиаров. "
titles = [p.get('title', '') for p in prs[:3] if p.get('title')]
if titles:
line += "Первые: " + ". ".join(titles) + "."
_speak(line)
def do_github_summarize_pr(action, voice):
repo = memory_store.recall("github.repo")
if not repo:
_speak("Сначала укажите репозиторий.")
return
# Most recent open PR number
ok, out, _ = _gh('pr', 'list', '--repo', repo, '--state', 'open',
'--json', 'number', '--limit', '1')
if not ok:
_speak("gh CLI не отвечает.")
return
try:
items = json.loads(out or '[]')
number = items[0]['number'] if items else None
except (json.JSONDecodeError, KeyError, IndexError):
number = None
if not number:
_speak("Открытых пиаров нет.")
return
ok, out, _ = _gh('pr', 'view', str(number), '--repo', repo,
'--json', 'title,body,additions,deletions,changedFiles,author')
if not ok:
_speak("Не получилось получить PR.")
return
try:
pr = json.loads(out)
except json.JSONDecodeError:
_speak("gh вернул битый JSON.")
return
title = pr.get('title', '(no title)')
body = (pr.get('body') or '')[:3000]
additions = pr.get('additions', '?')
deletions = pr.get('deletions', '?')
files = pr.get('changedFiles', '?')
author = (pr.get('author') or {}).get('login', '(unknown)')
feat: LLM hot-swap voice command + Ollama backend (148 → 151 commands) Closes the last functional parity gap with rust. Python now has voice-driven Ollama↔Groq switching, persistent across restarts. llm_backend.py (new, ~130 lines) Singleton module: current_client() → active OpenAI client (or None) current_backend() → 'groq' | 'ollama' | 'none' current_model() → active model name swap_to(name) → hot-swap + persist to <here>/llm_backend.txt parse_backend(name) → ru/en alias normaliser ('облако'→groq, 'локальный'→ollama) Backends: - Groq: config.GROQ_TOKEN + GROQ_BASE_URL + GROQ_MODEL (defaults) - Ollama: OLLAMA_BASE_URL (default http://localhost:11434/v1) + OLLAMA_MODEL (default qwen2.5:3b). api_key='ollama' (placeholder, openai lib insists on a non-empty string; Ollama ignores it). Init precedence (idempotent _ensure_init): 1. Persisted choice from llm_backend.txt 2. JARVIS_LLM env var 3. Auto-detect: Groq if token present, else Ollama Voice commands (commands.yaml, +3 entries → 151) llm_switch_local → ollama llm_switch_cloud → groq llm_status → speaks current backend extensions.py + do_llm_switch / do_llm_status handlers + llm_backend import do_interesting_fact migrated off direct OpenAI(...) to use llm_backend.current_* dev_handlers.py + llm_backend import do_codebase_ask + do_github_summarize_pr migrated to llm_backend.current_* vision_handler.py Vision call documented as Groq-specific (Ollama doesn't expose vision via OpenAI-compat in our stack), kept direct config.GROQ_TOKEN reading. Tests: ast.parse passes for all 5 modules. yaml.safe_load = 151 entries. PYTHON PARITY VS RUST IS NOW FUNCTIONALLY COMPLETE. Only remaining rust-only feature: the Tauri GUI (python is console-only).
2026-05-16 00:44:42 +03:00
client = llm_backend.current_client()
if client is None:
_speak(f"PR номер {number}: {title}. Без LLM — настройте Groq или Ollama.")
feat: codebase_qa + github_pr in Python (142 → 148 commands) Closes the Python parity gap with rust. All major imba features now mirrored EXCEPT live LLM hot-swap (python LLM backend is still env-driven, not voice- switchable like rust). dev_handlers.py (new, ~250 lines) Codebase Q&A: do_codebase_set "укажи проект C:\path" → memory_store.remember("codebase.root", ...) do_codebase_where "какой проект сейчас" do_codebase_ask "что делает функция X" / "найди в коде Y" Walks the root with same caps as rust: depth=3, files=30, file_bytes=4096, total=50000 Filters by extension (rs/py/ts/lua/go/...). Skips .git/target/node_modules/__pycache__/.venv etc. Feeds digest to Groq LLM with "senior reviewer" prompt. GitHub PR review (requires `gh` CLI authenticated): do_github_set_repo "текущий репо owner/repo" → memory_store do_github_list_prs "какие пиары" → `gh pr list --json number,title,author`, speaks count + first 3 titles do_github_summarize_pr "разбери последний пиар" → gh pr view of latest open PR, feeds title+body+diff stat to LLM, speaks 3-5 sentence review. extensions.py — 6 new proxy handlers + import dev_handlers + set_speak wires it. main.py — 6 new action_type dispatches in run_action. commands.yaml (+6 entries, 142 → 148): codebase_set, codebase_where, codebase_ask, github_set_repo, github_list_prs, github_summarize_pr. Tests: ast.parse passes for all .py files. yaml.safe_load = 148 entries. PYTHON PARITY VS RUST IS NOW COMPLETE except: ✗ LLM hot-swap voice command (rust calls llm::swap_to + persists DB; python config.GROQ_TOKEN is read once at startup) ✗ GUI pages (rust has Tauri+Svelte, python is console-only) Everything else has full parity: memory / profiles / vision / macros / scheduler / codebase Q&A / GitHub PR review / 14 utility packs.
2026-05-16 00:40:08 +03:00
return
feat: LLM hot-swap voice command + Ollama backend (148 → 151 commands) Closes the last functional parity gap with rust. Python now has voice-driven Ollama↔Groq switching, persistent across restarts. llm_backend.py (new, ~130 lines) Singleton module: current_client() → active OpenAI client (or None) current_backend() → 'groq' | 'ollama' | 'none' current_model() → active model name swap_to(name) → hot-swap + persist to <here>/llm_backend.txt parse_backend(name) → ru/en alias normaliser ('облако'→groq, 'локальный'→ollama) Backends: - Groq: config.GROQ_TOKEN + GROQ_BASE_URL + GROQ_MODEL (defaults) - Ollama: OLLAMA_BASE_URL (default http://localhost:11434/v1) + OLLAMA_MODEL (default qwen2.5:3b). api_key='ollama' (placeholder, openai lib insists on a non-empty string; Ollama ignores it). Init precedence (idempotent _ensure_init): 1. Persisted choice from llm_backend.txt 2. JARVIS_LLM env var 3. Auto-detect: Groq if token present, else Ollama Voice commands (commands.yaml, +3 entries → 151) llm_switch_local → ollama llm_switch_cloud → groq llm_status → speaks current backend extensions.py + do_llm_switch / do_llm_status handlers + llm_backend import do_interesting_fact migrated off direct OpenAI(...) to use llm_backend.current_* dev_handlers.py + llm_backend import do_codebase_ask + do_github_summarize_pr migrated to llm_backend.current_* vision_handler.py Vision call documented as Groq-specific (Ollama doesn't expose vision via OpenAI-compat in our stack), kept direct config.GROQ_TOKEN reading. Tests: ast.parse passes for all 5 modules. yaml.safe_load = 151 entries. PYTHON PARITY VS RUST IS NOW FUNCTIONALLY COMPLETE. Only remaining rust-only feature: the Tauri GUI (python is console-only).
2026-05-16 00:44:42 +03:00
model = llm_backend.current_model()
feat: codebase_qa + github_pr in Python (142 → 148 commands) Closes the Python parity gap with rust. All major imba features now mirrored EXCEPT live LLM hot-swap (python LLM backend is still env-driven, not voice- switchable like rust). dev_handlers.py (new, ~250 lines) Codebase Q&A: do_codebase_set "укажи проект C:\path" → memory_store.remember("codebase.root", ...) do_codebase_where "какой проект сейчас" do_codebase_ask "что делает функция X" / "найди в коде Y" Walks the root with same caps as rust: depth=3, files=30, file_bytes=4096, total=50000 Filters by extension (rs/py/ts/lua/go/...). Skips .git/target/node_modules/__pycache__/.venv etc. Feeds digest to Groq LLM with "senior reviewer" prompt. GitHub PR review (requires `gh` CLI authenticated): do_github_set_repo "текущий репо owner/repo" → memory_store do_github_list_prs "какие пиары" → `gh pr list --json number,title,author`, speaks count + first 3 titles do_github_summarize_pr "разбери последний пиар" → gh pr view of latest open PR, feeds title+body+diff stat to LLM, speaks 3-5 sentence review. extensions.py — 6 new proxy handlers + import dev_handlers + set_speak wires it. main.py — 6 new action_type dispatches in run_action. commands.yaml (+6 entries, 142 → 148): codebase_set, codebase_where, codebase_ask, github_set_repo, github_list_prs, github_summarize_pr. Tests: ast.parse passes for all .py files. yaml.safe_load = 148 entries. PYTHON PARITY VS RUST IS NOW COMPLETE except: ✗ LLM hot-swap voice command (rust calls llm::swap_to + persists DB; python config.GROQ_TOKEN is read once at startup) ✗ GUI pages (rust has Tauri+Svelte, python is console-only) Everything else has full parity: memory / profiles / vision / macros / scheduler / codebase Q&A / GitHub PR review / 14 utility packs.
2026-05-16 00:40:08 +03:00
user_prompt = (
f"Repo: {repo}\nPR #{number}: {title}\nAuthor: {author}\n"
f"Changed files: {files} (+{additions}, -{deletions})\n"
f"Description:\n{body}\n\n"
"Кратко (3-5 предложений) на русском: что меняет этот PR, "
"какие риски, стоит ли мёржить."
)
try:
resp = client.chat.completions.create(
feat: LLM hot-swap voice command + Ollama backend (148 → 151 commands) Closes the last functional parity gap with rust. Python now has voice-driven Ollama↔Groq switching, persistent across restarts. llm_backend.py (new, ~130 lines) Singleton module: current_client() → active OpenAI client (or None) current_backend() → 'groq' | 'ollama' | 'none' current_model() → active model name swap_to(name) → hot-swap + persist to <here>/llm_backend.txt parse_backend(name) → ru/en alias normaliser ('облако'→groq, 'локальный'→ollama) Backends: - Groq: config.GROQ_TOKEN + GROQ_BASE_URL + GROQ_MODEL (defaults) - Ollama: OLLAMA_BASE_URL (default http://localhost:11434/v1) + OLLAMA_MODEL (default qwen2.5:3b). api_key='ollama' (placeholder, openai lib insists on a non-empty string; Ollama ignores it). Init precedence (idempotent _ensure_init): 1. Persisted choice from llm_backend.txt 2. JARVIS_LLM env var 3. Auto-detect: Groq if token present, else Ollama Voice commands (commands.yaml, +3 entries → 151) llm_switch_local → ollama llm_switch_cloud → groq llm_status → speaks current backend extensions.py + do_llm_switch / do_llm_status handlers + llm_backend import do_interesting_fact migrated off direct OpenAI(...) to use llm_backend.current_* dev_handlers.py + llm_backend import do_codebase_ask + do_github_summarize_pr migrated to llm_backend.current_* vision_handler.py Vision call documented as Groq-specific (Ollama doesn't expose vision via OpenAI-compat in our stack), kept direct config.GROQ_TOKEN reading. Tests: ast.parse passes for all 5 modules. yaml.safe_load = 151 entries. PYTHON PARITY VS RUST IS NOW FUNCTIONALLY COMPLETE. Only remaining rust-only feature: the Tauri GUI (python is console-only).
2026-05-16 00:44:42 +03:00
model=model,
feat: codebase_qa + github_pr in Python (142 → 148 commands) Closes the Python parity gap with rust. All major imba features now mirrored EXCEPT live LLM hot-swap (python LLM backend is still env-driven, not voice- switchable like rust). dev_handlers.py (new, ~250 lines) Codebase Q&A: do_codebase_set "укажи проект C:\path" → memory_store.remember("codebase.root", ...) do_codebase_where "какой проект сейчас" do_codebase_ask "что делает функция X" / "найди в коде Y" Walks the root with same caps as rust: depth=3, files=30, file_bytes=4096, total=50000 Filters by extension (rs/py/ts/lua/go/...). Skips .git/target/node_modules/__pycache__/.venv etc. Feeds digest to Groq LLM with "senior reviewer" prompt. GitHub PR review (requires `gh` CLI authenticated): do_github_set_repo "текущий репо owner/repo" → memory_store do_github_list_prs "какие пиары" → `gh pr list --json number,title,author`, speaks count + first 3 titles do_github_summarize_pr "разбери последний пиар" → gh pr view of latest open PR, feeds title+body+diff stat to LLM, speaks 3-5 sentence review. extensions.py — 6 new proxy handlers + import dev_handlers + set_speak wires it. main.py — 6 new action_type dispatches in run_action. commands.yaml (+6 entries, 142 → 148): codebase_set, codebase_where, codebase_ask, github_set_repo, github_list_prs, github_summarize_pr. Tests: ast.parse passes for all .py files. yaml.safe_load = 148 entries. PYTHON PARITY VS RUST IS NOW COMPLETE except: ✗ LLM hot-swap voice command (rust calls llm::swap_to + persists DB; python config.GROQ_TOKEN is read once at startup) ✗ GUI pages (rust has Tauri+Svelte, python is console-only) Everything else has full parity: memory / profiles / vision / macros / scheduler / codebase Q&A / GitHub PR review / 14 utility packs.
2026-05-16 00:40:08 +03:00
messages=[
{'role': 'system', 'content': "Ты — старший разработчик, делаешь код-ревью. По делу, без воды."},
{'role': 'user', 'content': user_prompt},
],
max_tokens=400, temperature=0.2, timeout=45,
)
review = resp.choices[0].message.content.strip()
except Exception as exc:
print(f"[dev] PR LLM: {exc}")
_speak("Не получилось проанализировать.")
return
if not review:
_speak("Пустая сводка.")
return
_speak(f"Пиар номер {number}: {review}")