Files
bearingsinfo/tools/scripts/build_knowledge_base.py

580 lines
24 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()