textual — overkill для нашего TUI, rich.Live достаточно
libtmux — subprocess + tmux CLI проще и уже работает
keyboard — требует root на macOS, threading + stdin проще
asyncio — threading достаточно для наших 3-5 параллельных процессов
curses — Rich абстрагирует терминал лучше
2. Интерфейсные контракты
2.1 Новый модуль: whilly/decomposer.py
"""Task decomposition — анализ pending задач и split через LLM."""fromwhilly.task_managerimportTaskManagerfromwhilly.agent_runnerimportAgentResultdefneeds_decompose(tm:TaskManager)->bool:"""Эвристика: есть ли задачи, требующие декомпозиции.
Критерии:
- 6+ acceptance_criteria
- description содержит 2+ " и " или 1+ " + "
Returns: True если хотя бы одна pending задача подходит.
"""defbuild_decompose_prompt(tasks_file:str)->str:"""Промпт для LLM-агента декомпозиции.
Инструкции агенту:
- Анализировать pending задачи по критериям
- Разбить крупные на 2-5 подзадач (TASK-XXXa, TASK-XXXb)
- НЕ трогать done/in_progress/failed
- Обновить dependencies
- Вернуть <promise>DECOMPOSED N</promise> или <promise>NO_DECOMPOSE</promise>
"""defrun_decompose(tm:TaskManager,agent_model:str,use_tmux:bool,log_dir:Path,)->int:"""Запустить LLM декомпозицию.
Returns: количество добавленных задач (0 если без изменений).
Side effects: модифицирует tasks JSON файл.
"""# Cache — не повторять decompose если задачи не изменились
_last_decompose_hash:str=""def_tasks_hash(tm:TaskManager)->str:"""SHA256 от pending task IDs + descriptions. Для cache."""
2.2 Новый модуль: whilly/notifications.py
"""macOS voice notifications via `say` command."""importshutilimportsubprocessimportlogginglog=logging.getLogger("whilly")SAY_BIN:str|None=shutil.which("say")VOICE="Milena"# Russian voice
ENABLED=True# Overridden by WHILLY_VOICE=0
defnotify(text:str)->None:"""Произнести текст через macOS say. Noop если недоступно."""# Convenience shortcuts
defnotify_decompose(count:int)->None:"""'Декомпозиция: добавлено N задач.'"""defnotify_task_done()->None:"""'Задача готова. Продолжаю работу.'"""defnotify_plan_done()->None:"""'План завершён!'"""defnotify_all_done()->None:"""'Хозяин, я всё сделалъ!'"""
# Новый класс в dashboard.py
classKeyboardHandler:"""Non-blocking keyboard input через threading."""def__init__(self,dashboard:Dashboard):self._dashboard=dashboardself._thread:threading.Thread|None=Noneself._running=Falseself._callback:dict[str,Callable]={}defregister(self,key:str,callback:Callable)->None:"""Зарегистрировать callback для клавиши."""defstart(self)->None:"""Запустить listener thread."""defstop(self)->None:"""Остановить listener thread."""def_listen_loop(self)->None:"""Внутренний цикл чтения stdin в отдельном thread."""# Overlay views (возвращают текст для отображения)
defrender_task_detail(tm:TaskManager,task_id:str)->str:"""Полная информация о задаче: description, AC, test_steps, deps."""defrender_log_view(log_file:Path,lines:int=30)->str:"""Последние N строк лога."""defrender_all_tasks(tm:TaskManager)->str:"""Таблица всех задач с иконками статуса."""defrender_help(config:WhillyConfig)->str:"""Hotkeys + configuration + file paths."""
2.4 Обновление: whilly/config.py — новые поля
@dataclassclassWhillyConfig:# Существующие
MAX_ITERATIONS:int=0MAX_PARALLEL:int=3HEARTBEAT_INTERVAL:int=1DECOMPOSE_EVERY:int=5AGENT:str=""USE_TMUX:bool=TrueLOG_DIR:str="whilly_logs"MODEL:str="claude-opus-4-6[1m]"# Новые
VOICE:bool=True# F5: voice notifications
ORCHESTRATOR:str="file"# F4: "file" | "llm"
RICH_DASHBOARD:bool=True# F1: use Rich Live vs ANSI fallback
2.5 Обновление: whilly/orchestrator.py — LLM режим
# Добавить к существующему
defplan_batches_llm(ready_tasks:list[Task],max_parallel:int,tasks_file:str,agent_model:str,)->list[list[Task]]:"""LLM-based orchestration с fallback на file-based.
1. Формирует промпт с ready tasks
2. Запускает agent
3. Парсит JSON ответ (с json-repair для robustness)
4. Валидирует task IDs
5. При ошибке — fallback на plan_batches()
"""defbuild_orchestrator_prompt(ready_tasks:list[Task],max_parallel:int)->str:"""Промпт для LLM orchestrator."""defbuild_interface_agreement_prompt(module:str,task_ids:list[str],tasks_file:str)->str:"""Промпт для interface agreement между parallel задачами."""defrun_interface_agreement(module:str,task_ids:list[str],tasks_file:str,agent_model:str,log_dir:Path,)->None:"""Запустить LLM для определения интерфейсного контракта.
Результат сохраняется в .planning/interfaces/{module}_contract.md
"""
2.6 Обновление: whilly.py — интеграция новых модулей
# Новые импорты
fromwhilly.decomposerimportneeds_decompose,run_decomposefromwhilly.notificationsimportnotify_task_done,notify_plan_done,notify_all_donefromwhilly.dashboardimportDashboard,KeyboardHandler# Изменения в run_plan():
# 1. Перед main loop: initial decompose check
# 2. В main loop: periodic decompose (DECOMPOSE_EVERY)
# 3. После batch: voice notification
# 4. Rich Dashboard вместо ANSI fallback (если RICH_DASHBOARD=True)
# 5. KeyboardHandler для hotkeys