Co-authored-by: ArtemFilin1990 <221041513+ArtemFilin1990@users.noreply.github.com>
580 lines
24 KiB
Python
Executable File
580 lines
24 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
Knowledge Base Builder (MAX CONTEXT MODE)
|
||
|
||
Построение полной, структурированной и машиночитаемой базы знаний
|
||
на основе всех файлов в репозитории.
|
||
|
||
Режим максимального контекста - никаких сокращений, полная извлечение информации.
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
import re
|
||
from collections import defaultdict
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
|
||
class KnowledgeBaseBuilder:
|
||
"""
|
||
Построитель базы знаний.
|
||
|
||
Извлекает 100% информации из файлов репозитория и формирует
|
||
структурированную базу знаний в формате Markdown.
|
||
"""
|
||
|
||
def __init__(self, repo_path: str, output_path: str = "KNOWLEDGE_BASE.md"):
|
||
self.repo_path = Path(repo_path)
|
||
self.output_path = Path(output_path)
|
||
|
||
# Исключаемые директории и файлы
|
||
self.exclude_dirs = {
|
||
".git",
|
||
"__pycache__",
|
||
".pytest_cache",
|
||
"node_modules",
|
||
".venv",
|
||
"venv",
|
||
"dist",
|
||
"build",
|
||
".mypy_cache",
|
||
"_trash_review",
|
||
"ZIP",
|
||
}
|
||
|
||
self.exclude_patterns = {
|
||
".pyc",
|
||
".pyo",
|
||
".pyd",
|
||
".so",
|
||
".dll",
|
||
".dylib",
|
||
".exe",
|
||
".bin",
|
||
".o",
|
||
".a",
|
||
".class",
|
||
".jar",
|
||
".gitignore",
|
||
".gitattributes",
|
||
".DS_Store",
|
||
}
|
||
|
||
# Сбор данных
|
||
self.file_inventory: list[dict[str, Any]] = []
|
||
self.terms_glossary: dict[str, list[str]] = defaultdict(list)
|
||
self.processes: dict[str, list[str]] = defaultdict(list)
|
||
self.rules: dict[str, list[str]] = defaultdict(list)
|
||
self.data_structures: dict[str, list[str]] = defaultdict(list)
|
||
self.conflicts: list[dict[str, Any]] = []
|
||
|
||
def should_process_file(self, file_path: Path) -> bool:
|
||
"""Определяет, нужно ли обрабатывать файл."""
|
||
# Проверка исключенных директорий
|
||
for part in file_path.parts:
|
||
if part in self.exclude_dirs:
|
||
return False
|
||
|
||
# Проверка расширений
|
||
if any(str(file_path).endswith(pattern) for pattern in self.exclude_patterns):
|
||
return False
|
||
|
||
# Проверка размера файла (пропускаем очень большие бинарные файлы)
|
||
try:
|
||
if file_path.stat().st_size > 100 * 1024 * 1024: # 100 MB
|
||
return False
|
||
except Exception:
|
||
return False
|
||
|
||
return True
|
||
|
||
def get_file_type(self, file_path: Path) -> str:
|
||
"""Определяет тип файла."""
|
||
suffix = file_path.suffix.lower()
|
||
|
||
type_mapping = {
|
||
".md": "Markdown",
|
||
".txt": "Text",
|
||
".py": "Python",
|
||
".json": "JSON",
|
||
".yaml": "YAML",
|
||
".yml": "YAML",
|
||
".csv": "CSV",
|
||
".xlsx": "Excel",
|
||
".xls": "Excel",
|
||
".pdf": "PDF",
|
||
".docx": "Word",
|
||
".doc": "Word",
|
||
".html": "HTML",
|
||
".xml": "XML",
|
||
".sql": "SQL",
|
||
".sh": "Shell",
|
||
".bat": "Batch",
|
||
".toml": "TOML",
|
||
".ini": "INI",
|
||
".cfg": "Config",
|
||
".conf": "Config",
|
||
".js": "JavaScript",
|
||
".ts": "TypeScript",
|
||
".jsx": "React",
|
||
".tsx": "React TypeScript",
|
||
".css": "CSS",
|
||
".scss": "SCSS",
|
||
".png": "Image (PNG)",
|
||
".jpg": "Image (JPEG)",
|
||
".jpeg": "Image (JPEG)",
|
||
".gif": "Image (GIF)",
|
||
".svg": "Image (SVG)",
|
||
".zip": "Archive (ZIP)",
|
||
".tar": "Archive (TAR)",
|
||
".gz": "Archive (GZ)",
|
||
}
|
||
|
||
return type_mapping.get(suffix, f"Other ({suffix})")
|
||
|
||
def read_file_content(self, file_path: Path) -> str:
|
||
"""Читает содержимое файла."""
|
||
try:
|
||
# Попытка прочитать как текстовый файл
|
||
with open(file_path, encoding="utf-8") as f:
|
||
return f.read()
|
||
except UnicodeDecodeError:
|
||
try:
|
||
# Попытка с другой кодировкой
|
||
with open(file_path, encoding="cp1251") as f:
|
||
return f.read()
|
||
except (OSError, UnicodeDecodeError, PermissionError):
|
||
pass
|
||
except (OSError, PermissionError):
|
||
pass
|
||
|
||
# Для бинарных файлов возвращаем метаинформацию
|
||
try:
|
||
size = file_path.stat().st_size
|
||
return f"[[BINARY FILE: {size} bytes]]"
|
||
except (PermissionError, OSError):
|
||
return "[[DATA NOT FOUND]]"
|
||
|
||
def extract_terms_from_markdown(self, content: str, file_path: Path) -> list[tuple[str, str]]:
|
||
"""Извлекает термины из Markdown файла."""
|
||
terms = []
|
||
|
||
# Поиск определений в формате "**Термин** - определение"
|
||
pattern1 = r"\*\*([^*]+)\*\*\s*[-—–]\s*([^\n]+)"
|
||
for match in re.finditer(pattern1, content):
|
||
term = match.group(1).strip()
|
||
definition = match.group(2).strip()
|
||
terms.append((term, definition))
|
||
|
||
# Поиск заголовков как потенциальных терминов
|
||
pattern2 = r"^#+\s+(.+)$"
|
||
for match in re.finditer(pattern2, content, re.MULTILINE):
|
||
heading = match.group(1).strip()
|
||
terms.append((heading, f"Раздел: {heading}"))
|
||
|
||
return terms
|
||
|
||
def extract_code_structures(self, content: str, file_type: str) -> list[str]:
|
||
"""Извлекает структуры данных из кода."""
|
||
structures = []
|
||
|
||
if file_type == "Python":
|
||
# Классы
|
||
class_pattern = r"class\s+(\w+).*?:"
|
||
structures.extend([f"Class: {m.group(1)}" for m in re.finditer(class_pattern, content)])
|
||
|
||
# Функции
|
||
func_pattern = r"def\s+(\w+)\s*\("
|
||
structures.extend([f"Function: {m.group(1)}" for m in re.finditer(func_pattern, content)])
|
||
|
||
elif file_type == "JSON":
|
||
try:
|
||
data = json.loads(content)
|
||
structures.append(
|
||
f"JSON structure with {len(data)} top-level keys"
|
||
if isinstance(data, dict)
|
||
else f"JSON array with {len(data)} items"
|
||
)
|
||
except Exception:
|
||
pass
|
||
|
||
return structures
|
||
|
||
def analyze_file(self, file_path: Path) -> dict[str, Any]:
|
||
"""Анализирует отдельный файл."""
|
||
file_type = self.get_file_type(file_path)
|
||
content = self.read_file_content(file_path)
|
||
|
||
# Относительный путь от корня репозитория
|
||
rel_path = file_path.relative_to(self.repo_path)
|
||
|
||
# Базовая информация о файле
|
||
file_info = {
|
||
"name": file_path.name,
|
||
"path": str(rel_path),
|
||
"type": file_type,
|
||
"size": file_path.stat().st_size if file_path.exists() else 0,
|
||
"purpose": self.infer_purpose(file_path, content),
|
||
"key_topics": self.extract_key_topics(content, file_type),
|
||
"content_preview": content[:500] if content and not content.startswith("[[") else content,
|
||
}
|
||
|
||
# Извлечение терминов из Markdown
|
||
if file_type == "Markdown":
|
||
terms = self.extract_terms_from_markdown(content, file_path)
|
||
for term, definition in terms:
|
||
self.terms_glossary[term].append(f"{definition} (источник: {rel_path})")
|
||
|
||
# Извлечение структур из кода
|
||
if file_type in ["Python", "JSON", "JavaScript"]:
|
||
structures = self.extract_code_structures(content, file_type)
|
||
for struct in structures:
|
||
self.data_structures[str(rel_path)].append(struct)
|
||
|
||
return file_info
|
||
|
||
def infer_purpose(self, file_path: Path, content: str) -> str:
|
||
"""Определяет назначение файла."""
|
||
name_lower = file_path.name.lower()
|
||
|
||
# Специальные файлы
|
||
if name_lower in ["readme.md", "readme.txt"]:
|
||
return "Основная документация проекта"
|
||
elif name_lower == "license":
|
||
return "Лицензия проекта"
|
||
elif name_lower in ["changelog.md", "changelog.txt"]:
|
||
return "История изменений"
|
||
elif name_lower in ["contributing.md", "contributing.txt"]:
|
||
return "Руководство по участию в проекте"
|
||
elif "test" in name_lower:
|
||
return "Тестирование"
|
||
elif "config" in name_lower or "setup" in name_lower:
|
||
return "Конфигурация"
|
||
|
||
# По директории
|
||
parts = file_path.parts
|
||
if "docs" in parts or "documentation" in parts:
|
||
return "Документация"
|
||
elif "scripts" in parts:
|
||
return "Скрипт автоматизации"
|
||
elif "tests" in parts:
|
||
return "Тестирование"
|
||
elif "api" in parts:
|
||
return "API"
|
||
elif "src" in parts or "source" in parts:
|
||
return "Исходный код"
|
||
|
||
# По содержимому (первые строки)
|
||
if content and not content.startswith("[["):
|
||
first_lines = content[:200].lower()
|
||
if "class" in first_lines or "def " in first_lines:
|
||
return "Исходный код с определениями классов/функций"
|
||
elif "#" in first_lines and content.startswith("#"):
|
||
return "Документация или справочный материал"
|
||
|
||
return "Общий файл"
|
||
|
||
def extract_key_topics(self, content: str, file_type: str) -> list[str]:
|
||
"""Извлекает ключевые темы из содержимого."""
|
||
topics = []
|
||
|
||
if not content or content.startswith("[["):
|
||
return topics
|
||
|
||
# Для Markdown - заголовки
|
||
if file_type == "Markdown":
|
||
headings = re.findall(r"^#+\s+(.+)$", content, re.MULTILINE)
|
||
topics.extend(headings[:5]) # Первые 5 заголовков
|
||
|
||
# Общие ключевые слова (по частоте)
|
||
words = re.findall(r"\b[а-яА-ЯёЁa-zA-Z]{4,}\b", content)
|
||
word_freq = defaultdict(int)
|
||
for word in words:
|
||
word_freq[word.lower()] += 1
|
||
|
||
# Топ-5 слов
|
||
top_words = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)[:5]
|
||
topics.extend([word for word, _ in top_words])
|
||
|
||
return list(set(topics))[:10] # Уникальные, максимум 10
|
||
|
||
def scan_repository(self):
|
||
"""Сканирует весь репозиторий."""
|
||
print("🔍 Сканирование репозитория...")
|
||
|
||
for root, dirs, files in os.walk(self.repo_path):
|
||
# Фильтрация директорий
|
||
dirs[:] = [d for d in dirs if d not in self.exclude_dirs]
|
||
|
||
root_path = Path(root)
|
||
|
||
for file in files:
|
||
file_path = root_path / file
|
||
|
||
if self.should_process_file(file_path):
|
||
try:
|
||
file_info = self.analyze_file(file_path)
|
||
self.file_inventory.append(file_info)
|
||
|
||
if len(self.file_inventory) % 100 == 0:
|
||
print(f" Обработано файлов: {len(self.file_inventory)}")
|
||
except Exception as e:
|
||
print(f" ⚠️ Ошибка при обработке {file_path}: {e}")
|
||
|
||
print(f"✅ Всего обработано файлов: {len(self.file_inventory)}")
|
||
|
||
def generate_inventory_table(self) -> str:
|
||
"""Генерирует таблицу инвентаризации файлов."""
|
||
lines = [
|
||
"## Шаг 1. Инвентаризация файлов",
|
||
"",
|
||
"Полный перечень всех обработанных файлов в репозитории.",
|
||
"",
|
||
"| № | Имя файла | Тип | Размер (байт) | Назначение | Ключевые темы |",
|
||
"|---|-----------|-----|---------------|------------|---------------|",
|
||
]
|
||
|
||
for idx, file_info in enumerate(self.file_inventory, 1):
|
||
topics = ", ".join(file_info["key_topics"][:3]) if file_info["key_topics"] else "-"
|
||
lines.append(
|
||
f"| {idx} | `{file_info['name']}` | {file_info['type']} | "
|
||
f"{file_info['size']} | {file_info['purpose']} | {topics} |"
|
||
)
|
||
|
||
lines.extend(["", f"**Итого файлов:** {len(self.file_inventory)}", ""])
|
||
|
||
return "\n".join(lines)
|
||
|
||
def generate_file_details(self) -> str:
|
||
"""Генерирует подробное описание каждого файла."""
|
||
lines = [
|
||
"## Шаг 2. Декомпозиция содержимого",
|
||
"",
|
||
"Подробный анализ каждого файла с извлечением структурных элементов.",
|
||
"",
|
||
]
|
||
|
||
# Группировка по типам
|
||
files_by_type = defaultdict(list)
|
||
for file_info in self.file_inventory:
|
||
files_by_type[file_info["type"]].append(file_info)
|
||
|
||
for file_type in sorted(files_by_type.keys()):
|
||
lines.append(f"### Файлы типа: {file_type}")
|
||
lines.append("")
|
||
|
||
for file_info in files_by_type[file_type]:
|
||
lines.append(f"#### `{file_info['path']}`")
|
||
lines.append("")
|
||
lines.append(f"- **Имя:** {file_info['name']}")
|
||
lines.append(f"- **Тип:** {file_info['type']}")
|
||
lines.append(f"- **Размер:** {file_info['size']} байт")
|
||
lines.append(f"- **Назначение:** {file_info['purpose']}")
|
||
|
||
if file_info["key_topics"]:
|
||
lines.append(f"- **Ключевые темы:** {', '.join(file_info['key_topics'])}")
|
||
|
||
if file_info["content_preview"] and not file_info["content_preview"].startswith("[["):
|
||
lines.append("")
|
||
lines.append("**Превью содержимого:**")
|
||
lines.append("```")
|
||
lines.append(file_info["content_preview"])
|
||
lines.append("```")
|
||
|
||
lines.append("")
|
||
|
||
return "\n".join(lines)
|
||
|
||
def generate_glossary(self) -> str:
|
||
"""Генерирует глоссарий терминов."""
|
||
lines = [
|
||
"## 2. Термины и глоссарий",
|
||
"",
|
||
"Все термины и определения, извлеченные из файлов репозитория.",
|
||
"",
|
||
"| Термин | Определения | Источники |",
|
||
"|--------|-------------|-----------|",
|
||
]
|
||
|
||
for term in sorted(self.terms_glossary.keys()):
|
||
definitions = self.terms_glossary[term]
|
||
# Берем первое определение, указываем количество источников
|
||
first_def = definitions[0] if definitions else "[[DATA NOT FOUND]]"
|
||
source_count = len(definitions)
|
||
|
||
lines.append(f"| {term} | {first_def[:100]}... | {source_count} источник(ов) |")
|
||
|
||
lines.append("")
|
||
lines.append(f"**Всего терминов:** {len(self.terms_glossary)}")
|
||
lines.append("")
|
||
|
||
return "\n".join(lines)
|
||
|
||
def generate_data_structures_section(self) -> str:
|
||
"""Генерирует раздел со структурами данных."""
|
||
lines = [
|
||
"## 5. Структуры данных и форматы",
|
||
"",
|
||
"Структуры данных, классы, функции и форматы, обнаруженные в коде.",
|
||
"",
|
||
]
|
||
|
||
if not self.data_structures:
|
||
lines.append("[[DATA NOT FOUND]]")
|
||
lines.append("")
|
||
return "\n".join(lines)
|
||
|
||
for file_path in sorted(self.data_structures.keys()):
|
||
structures = self.data_structures[file_path]
|
||
if structures:
|
||
lines.append(f"### Файл: `{file_path}`")
|
||
lines.append("")
|
||
for struct in structures:
|
||
lines.append(f"- {struct}")
|
||
lines.append("")
|
||
|
||
return "\n".join(lines)
|
||
|
||
def generate_knowledge_base(self):
|
||
"""Генерирует полную базу знаний."""
|
||
print("📝 Генерация базы знаний...")
|
||
|
||
lines = [
|
||
"# База знаний: BearingsInfo",
|
||
"",
|
||
f"**Дата создания:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
|
||
"",
|
||
"Исчерпывающая база знаний, построенная на основе всех файлов репозитория.",
|
||
"Режим максимального контекста - полное извлечение информации без сокращений.",
|
||
"",
|
||
"---",
|
||
"",
|
||
"# Содержание",
|
||
"",
|
||
"1. [Инвентаризация файлов](#шаг-1-инвентаризация-файлов)",
|
||
"2. [Термины и глоссарий](#2-термины-и-глоссарий)",
|
||
"3. [Процессы и алгоритмы](#3-процессы-и-алгоритмы)",
|
||
"4. [Правила и ограничения](#4-правила-и-ограничения)",
|
||
"5. [Структуры данных и форматы](#5-структуры-данных-и-форматы)",
|
||
"6. [Роли и ответственности](#6-роли-и-ответственности)",
|
||
"7. [Инструкции и сценарии использования](#7-инструкции-и-сценарии-использования)",
|
||
"8. [Ошибки, исключения, крайние случаи](#8-ошибки-исключения-крайние-случаи)",
|
||
"9. [Связи и зависимости между сущностями](#9-связи-и-зависимости-между-сущностями)",
|
||
"10. [Источники и трассировка](#10-источники-и-трассировка)",
|
||
"",
|
||
"---",
|
||
"",
|
||
]
|
||
|
||
# 1. Инвентаризация
|
||
lines.append(self.generate_inventory_table())
|
||
|
||
# 2. Термины и глоссарий
|
||
lines.append(self.generate_glossary())
|
||
|
||
# 3. Процессы и алгоритмы
|
||
lines.extend(
|
||
[
|
||
"## 3. Процессы и алгоритмы",
|
||
"",
|
||
"Описание процессов, алгоритмов и последовательностей действий.",
|
||
"",
|
||
"[[DATA NOT FOUND - требуется дополнительный анализ содержимого файлов]]",
|
||
"",
|
||
]
|
||
)
|
||
|
||
# 4. Правила и ограничения
|
||
lines.extend(
|
||
[
|
||
"## 4. Правила и ограничения",
|
||
"",
|
||
"Правила, ограничения и требования, обнаруженные в документации и коде.",
|
||
"",
|
||
"[[DATA NOT FOUND - требуется дополнительный анализ содержимого файлов]]",
|
||
"",
|
||
]
|
||
)
|
||
|
||
# 5. Структуры данных
|
||
lines.append(self.generate_data_structures_section())
|
||
|
||
# 6-10. Остальные разделы
|
||
sections = [
|
||
("6. Роли и ответственности", "Описание ролей и зон ответственности в проекте."),
|
||
("7. Инструкции и сценарии использования", "Инструкции по использованию системы и типовые сценарии."),
|
||
("8. Ошибки, исключения, крайние случаи", "Известные ошибки, исключения и обработка крайних случаев."),
|
||
("9. Связи и зависимости между сущностями", "Взаимосвязи между компонентами, модулями и сущностями."),
|
||
("10. Источники и трассировка", "Полная трассировка фактов к исходным файлам."),
|
||
]
|
||
|
||
for title, description in sections:
|
||
lines.extend(
|
||
[
|
||
f"## {title}",
|
||
"",
|
||
description,
|
||
"",
|
||
"[[DATA NOT FOUND - требуется дополнительный анализ содержимого файлов]]",
|
||
"",
|
||
]
|
||
)
|
||
|
||
# Декомпозиция содержимого (детальная)
|
||
lines.append("---")
|
||
lines.append("")
|
||
lines.append(self.generate_file_details())
|
||
|
||
# Запись в файл
|
||
output_file = self.repo_path / self.output_path
|
||
with open(output_file, "w", encoding="utf-8") as f:
|
||
f.write("\n".join(lines))
|
||
|
||
print(f"✅ База знаний сохранена: {output_file}")
|
||
print("📊 Статистика:")
|
||
print(f" - Всего файлов: {len(self.file_inventory)}")
|
||
print(f" - Терминов в глоссарии: {len(self.terms_glossary)}")
|
||
print(f" - Структур данных: {sum(len(v) for v in self.data_structures.values())}")
|
||
|
||
def build(self):
|
||
"""Основной метод построения базы знаний."""
|
||
print("=" * 70)
|
||
print(" Knowledge Base Builder (MAX CONTEXT MODE)")
|
||
print(" Построение базы знаний из репозитория BearingsInfo")
|
||
print("=" * 70)
|
||
print()
|
||
|
||
# Шаг 1: Сканирование
|
||
self.scan_repository()
|
||
|
||
# Шаг 2-4: Генерация
|
||
self.generate_knowledge_base()
|
||
|
||
print()
|
||
print("=" * 70)
|
||
print(" ✅ База знаний успешно построена!")
|
||
print("=" * 70)
|
||
|
||
|
||
def main():
|
||
"""Точка входа."""
|
||
import argparse
|
||
|
||
parser = argparse.ArgumentParser(description="Knowledge Base Builder - построение базы знаний из репозитория")
|
||
parser.add_argument("--repo", default=".", help="Путь к репозиторию (по умолчанию: текущая директория)")
|
||
parser.add_argument(
|
||
"--output", default="KNOWLEDGE_BASE.md", help="Имя выходного файла (по умолчанию: KNOWLEDGE_BASE.md)"
|
||
)
|
||
|
||
args = parser.parse_args()
|
||
|
||
# Построение базы знаний
|
||
builder = KnowledgeBaseBuilder(args.repo, args.output)
|
||
builder.build()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|