@ -19,9 +19,11 @@ COLOR_RED = "\033[31m" # красный (ошибки)
COLOR_CYAN = " \033 [36m " # голубой (акценты, откат)
COLOR_BOLD = " \033 [1m "
def color_text ( text , color = COLOR_RESET ) :
return f " { color } { text } { COLOR_RESET } "
# ---------- Функции с б о р а проекта ----------
def collect_project_files ( root_dir , extensions = ( ' .py ' , ' .html ' ) ,
exclude_dirs = ( ' venv ' , ' __pycache__ ' , ' .git ' , ' env ' , ' node_modules ' ) ) :
@ -47,6 +49,7 @@ def collect_project_files(root_dir, extensions=('.py', '.html'),
files_data . append ( { ' path ' : rel_path , ' content ' : content } )
return files_data
def build_tree ( files_paths ) :
tree = defaultdict ( dict )
for path in sorted ( files_paths ) :
@ -70,8 +73,10 @@ def build_tree(files_paths):
extension = ' ' if is_last else ' │ '
lines . extend ( render ( node [ key ] , prefix + extension ) )
return lines
return ' \n ' . join ( render ( tree ) )
def build_prompt ( main_prompt , files_data ) :
parts = [ ]
parts . append ( " = " * 80 )
@ -154,7 +159,7 @@ def extract_json_from_text(text):
end = text . rfind ( ' } ' )
if end == - 1 :
return None
json_candidate = text [ start : end + 1 ]
json_candidate = text [ start : end + 1 ]
try :
return json . loads ( json_candidate )
except json . JSONDecodeError :
@ -350,7 +355,8 @@ def rollback_last_apply(project_root, ai_response_dir):
backup_files . sort ( key = lambda x : x [ 1 ] , reverse = True )
latest_backup = backup_files [ 0 ] [ 0 ]
shutil . copy2 ( latest_backup , abs_path )
reports . append ( f " ♻️ Восстановлен (откат update): { file_path } — из { os . path . basename ( latest_backup ) } " )
reports . append (
f " ♻️ Восстановлен (откат update): { file_path } — из { os . path . basename ( latest_backup ) } " )
else :
reports . append ( f " ❌ Н е найден бэкап для { file_path } , пропускаем (откат update) " )
errors = True
@ -367,7 +373,8 @@ def rollback_last_apply(project_root, ai_response_dir):
latest_backup = backup_files [ 0 ] [ 0 ]
os . makedirs ( os . path . dirname ( abs_path ) , exist_ok = True )
shutil . copy2 ( latest_backup , abs_path )
reports . append ( f " ♻️ Восстановлен (откат delete): { file_path } — из { os . path . basename ( latest_backup ) } " )
reports . append (
f " ♻️ Восстановлен (откат delete): { file_path } — из { os . path . basename ( latest_backup ) } " )
else :
reports . append ( f " ❌ Н е найден бэкап для { file_path } , пропускаем (откат delete) " )
errors = True
@ -392,6 +399,7 @@ def get_downloads_folder():
return str ( alt )
return os . getcwd ( )
def find_latest_json_file ( directory , prefix = ' deepseek_json_ ' ) :
files = [ ]
for f in os . listdir ( directory ) :
@ -404,6 +412,7 @@ def find_latest_json_file(directory, prefix='deepseek_json_'):
files . sort ( key = lambda x : x [ 1 ] , reverse = True )
return files [ 0 ] [ 0 ]
def run_tests ( project_root , test_path = None ) :
reports = [ ]
manage_path = os . path . join ( project_root , ' manage.py ' )
@ -473,7 +482,8 @@ def main():
if mode == ' 1 ' :
print ( color_text ( " \n С б о р информации о проекте для отправки в DeepSeek." , COLOR_LIGHT_GRAY ) )
main_prompt = input ( color_text ( " Введите ваш промт (одна строка, Enter для завершения): " , COLOR_YELLOW ) ) . strip ( )
main_prompt = input (
color_text ( " Введите ваш промт (одна строка, Enter для завершения): " , COLOR_YELLOW ) ) . strip ( )
if not main_prompt :
print ( color_text ( " Промт не может быть пустым. Завершение. " , COLOR_RED ) )
continue
@ -498,8 +508,11 @@ def main():
print ( color_text ( f " В с е г о собрано файлов: { len ( files_data ) } " , COLOR_GRAY ) )
print ( color_text ( " \n Теперь скопируйте содержимое этого файла и отправьте в DeepSeek. " , COLOR_YELLOW ) )
print ( color_text ( " DeepSeek вернёт ответ с описанием и JSON-блоком. " , COLOR_YELLOW ) )
print ( color_text ( " Скопируйте весь ответ и сохраните в файл с именем deepseek_json_YYYYMMDD_HHMMSS.json в папке Downloads. " , COLOR_YELLOW ) )
print ( color_text ( " Затем запустите этот скрипт в режиме 2 для автоматического применения. " , COLOR_YELLOW ) )
print ( color_text (
" Скопируйте весь ответ и сохраните в файл с именем deepseek_json_YYYYMMDD_HHMMSS.json в папке Downloads. " ,
COLOR_YELLOW ) )
print (
color_text ( " Затем запустите этот скрипт в режиме 2 для автоматического применения. " , COLOR_YELLOW ) )
except Exception as e :
print ( color_text ( f " ❌ Ошибка при сохранении: { e } " , COLOR_RED ) )
@ -507,7 +520,8 @@ def main():
downloads_dir = get_downloads_folder ( )
json_file = find_latest_json_file ( downloads_dir )
if json_file is None :
print ( color_text ( f " ❌ Н е найден JSON-файл с префиксом ' deepseek_json_ ' в папке { downloads_dir } " , COLOR_RED ) )
print ( color_text ( f " ❌ Н е найден JSON-файл с префиксом ' deepseek_json_ ' в папке { downloads_dir } " ,
COLOR_RED ) )
continue
print ( color_text ( f " 📂 Найден файл: { json_file } " , COLOR_GRAY ) )
@ -521,7 +535,9 @@ def main():
data = extract_json_from_text ( text )
if data is None :
print ( color_text ( " ❌ Н е удалось извлечь JSON из ответа. Убедитесь, что в ответе есть блок ```json ... ``` или валидный JSON. " , COLOR_RED ) )
print ( color_text (
" ❌ Н е удалось извлечь JSON из ответа. Убедитесь, что в ответе есть блок ```json ... ``` или валидный JSON. " ,
COLOR_RED ) )
continue
actions = data . get ( ' actions ' )
@ -579,7 +595,9 @@ def main():
print ( color_text ( f " \n 📄 Лог изменений записан в: { log_file } " , COLOR_GRAY ) )
elif mode == ' 3 ' :
test_path = input ( color_text ( " Введите относительный путь к папке или файлу с тестами (оставьте пустым для всех тестов): " , COLOR_YELLOW ) ) . strip ( )
test_path = input (
color_text ( " Введите относительный путь к папке или файлу с тестами (оставьте пустым для всех тестов): " ,
COLOR_YELLOW ) ) . strip ( )
if test_path == ' ' :
test_path = None
@ -603,7 +621,8 @@ def main():
f " Результат: { ' ✅ УСПЕШНО ' if success else ' ❌ ОШИБКА ' } " ,
f " Полный отчёт сохранён в: { test_report_file } "
]
write_log_entry ( log_file , ' TEST ' , brief_lines , extra_info = f " Путь: { test_path if test_path else ' все тесты ' } " )
write_log_entry ( log_file , ' TEST ' , brief_lines ,
extra_info = f " Путь: { test_path if test_path else ' все тесты ' } " )
print ( color_text ( " \n " + " = " * 60 , COLOR_WHITE + COLOR_BOLD ) )
print ( color_text ( " РЕЗУЛЬТАТ ТЕСТИРОВАНИЯ " , COLOR_WHITE + COLOR_BOLD ) )
@ -663,7 +682,8 @@ def main():
for idx , ( path , dt , name ) in enumerate ( backups , 1 ) :
print ( color_text ( f " { idx } . { name } ( { dt . strftime ( ' % Y- % m- %d % H: % M: % S ' ) } ) " , COLOR_GRAY ) )
choice = input ( color_text ( " Выберите номер бэкапа для восстановления (или 0 для отмены): " , COLOR_YELLOW ) ) . strip ( )
choice = input (
color_text ( " Выберите номер бэкапа для восстановления (или 0 для отмены): " , COLOR_YELLOW ) ) . strip ( )
if choice == ' 0 ' :
continue
try :
@ -676,7 +696,9 @@ def main():
print ( color_text ( " ❌ Неверный ввод. " , COLOR_RED ) )
continue
print ( color_text ( f " \n ⚠️ Восстановление из бэкапа { os . path . basename ( backup_path ) } полностью заменит текущий проект. " , COLOR_YELLOW ) )
print ( color_text (
f " \n ⚠️ Восстановление из бэкапа { os . path . basename ( backup_path ) } полностью заменит текущий проект. " ,
COLOR_YELLOW ) )
confirm = input ( color_text ( " Вы уверены? (введите ' да ' для подтверждения): " , COLOR_YELLOW ) ) . strip ( ) . lower ( )
if confirm != ' да ' :
print ( color_text ( " Восстановление отменено. " , COLOR_GRAY ) )
@ -687,12 +709,14 @@ def main():
print ( color_text ( f " \n ✅ Проект восстановлен из бэкапа: { os . path . basename ( backup_path ) } " , COLOR_GREEN ) )
today = datetime . now ( ) . strftime ( ' % Y- % m- %d ' )
log_file = os . path . join ( ai_response_dir , f ' ai_log_ { today } .txt ' )
write_log_entry ( log_file , ' RESTORE_FULL ' , [ f " Восстановлен проект из { os . path . basename ( backup_path ) } " ] , extra_info = " Полное восстановление " )
write_log_entry ( log_file , ' RESTORE_FULL ' , [ f " Восстановлен проект из { os . path . basename ( backup_path ) } " ] ,
extra_info = " Полное восстановление " )
except Exception as e :
print ( color_text ( f " ❌ Ошибка при восстановлении: { e } " , COLOR_RED ) )
else :
print ( color_text ( " ❌ Неверный выбор. Пожалуйста, введите число от 0 до 5. " , COLOR_RED ) )
if __name__ == " __main__ " :
main ( )