Skip to content

диздоки БС, БСС, БС контейнеров #23

диздоки БС, БСС, БС контейнеров

диздоки БС, БСС, БС контейнеров #23

name: Universal Lore Checker
on:
pull_request_target:
paths:
- '01_Вселенная/**'
- '02_Объекты/**'
- '03_Государства/**'
- '04_Корпорации/**'
- '05_Организации/**'
- '06_Расы/**'
- '07_Существа/**'
- '08_Технологии/**'
- '09_Вооружение/**'
- '10_Явления/**'
- '11_Товары/**'
- '12_Другое/**'
- '13_Истории/**'
- '14_Повседневность/**'
jobs:
universal_lore_checker:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Fetch PR head branch
run: |
git remote add pr "${{ github.event.pull_request.head.repo.clone_url }}"
git fetch pr "${{ github.event.pull_request.head.ref }}"
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
pip install --upgrade pip
pip cache purge
pip install google-generativeai==0.4.0
pip install requests
pip install google-ai-generativelanguage==0.4.0
pip install google-auth>=2.15.0
pip install google-api-core
pip install protobuf
pip install pydantic
pip install tqdm
pip install typing-extensions
pip install "proto-plus>=1.22.3"
pip install "googleapis-common-protos>=1.56.2"
pip install requests>=2.18.0
pip install cachetools>=2.0.0
pip install pyasn1-modules>=0.2.1
pip install rsa>=3.1.4
pip install annotated-types>=0.6.0
pip install pydantic-core
pip install grpcio>=1.33.2
pip install grpcio-status>=1.33.2
pip install pyasn1>=0.4.6
- name: Analyze Markdown Changes and Compare with Templates, Rules, and other articles
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
python <<'EOF'
import os, subprocess, requests, re
import google.generativeai as genai
# Настройка модели Gemini
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
model = genai.GenerativeModel("gemini-2.0-flash")
def get_changed_files():
"""
Вычисляет общий предок между базовой веткой и веткой PR,
а затем получает список изменённых файлов с указанием статуса (A/M) через git diff.
Возвращает список кортежей (status, filename) для файлов с расширением .md.
"""
base_sha = os.getenv("BASE_SHA")
head_ref = os.getenv("HEAD_REF")
head_sha = subprocess.check_output(["git", "rev-parse", f"pr/{head_ref}"]).strip().decode()
merge_base = subprocess.check_output(["git", "merge-base", base_sha, head_sha]).strip().decode()
diff_output = subprocess.check_output(
["git", "diff", "--name-status", "--diff-filter=ACMRT", merge_base, head_sha]
).decode().splitlines()
changed = []
for line in diff_output:
parts = line.split()
if len(parts) >= 2 and parts[1].endswith(".md"):
status, filename = parts[0], parts[1]
changed.append((status, filename))
return changed
def get_file_content(filepath):
"""
Получает содержимое файла из ветки PR с помощью git show.
"""
head_ref = os.getenv("HEAD_REF")
try:
result = subprocess.run(
["git", "show", f"pr/{head_ref}:{filepath}"],
capture_output=True, text=True, check=True
)
return result.stdout
except Exception as e:
print(f"Error getting content for {filepath}: {e}")
return None
def create_comment(body):
repo = os.getenv("GITHUB_REPOSITORY")
pr_number = os.getenv("GITHUB_REF").split('/')[-2]
url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
headers = {
"Authorization": f"Bearer {os.getenv('GITHUB_TOKEN')}",
"Accept": "application/vnd.github+json",
}
payload = {"body": body}
response = requests.post(url, json=payload, headers=headers)
if response.status_code != 201:
print(f"Failed to post comment: {response.status_code}, {response.text}")
else:
print("Comment posted successfully")
def get_lore_files(root_dir, exclude_dirs):
lore_files = {}
for root, _, files in os.walk(root_dir):
if any(ex_dir in root for ex_dir in exclude_dirs):
continue
for file in files:
if file.endswith('.md') and file not in ["README.md", "TEMPLATE.md", "design_document.md"]:
full_path = os.path.join(root, file)
try:
with open(full_path, 'r', encoding='utf-8') as f:
lore_files[full_path] = f.read()
except Exception as e:
print(f"Error reading {full_path}: {e}")
return lore_files
changed_files = get_changed_files()
for status, filepath in changed_files:
comments = []
dirname = os.path.dirname(filepath)
root_dirname = dirname.split(os.sep)[0]
file_name = os.path.basename(filepath)
# Если файл design_document.md обрабатывается отдельно
if file_name == "design_document.md":
if status == 'A':
try:
file_content = get_file_content(filepath)
rules_path = "00_Правила_оформления/Общие_правила.md"
with open(rules_path, 'r', encoding='utf-8') as r:
rules_content = r.read()
prompt_rules = f"""
Проанализируй следующий текст на соответствие общим правилам оформления.
Укажи, чего не хватает в тексте по сравнению с правилами.
Сформулируй рекомендации на русском языке.
Текст файла:
{file_content}
Общие правила:
{rules_content}
"""
response_rules = model.generate_content(prompt_rules)
recommendations_rules_text = (response_rules.candidates[0].content.parts[0].text.strip()
if response_rules.candidates and response_rules.candidates[0].content.parts
else "Не удалось получить рекомендации.")
comments.append(f"<details><summary>Проверка design_document.md на соответствие общим правилам</summary>\n\n{recommendations_rules_text}\n</details>")
except Exception as e:
comments.append(f"<details><summary>Ошибка проверки design_document.md</summary>\n\n{e}\n</details>")
if comments:
comment_body = f"### Анализ файла `{file_name}` в папке `{root_dirname}`:\n\n" + "\n".join(comments)
create_comment(comment_body)
continue
# 0. Краткий пересказ файла
try:
file_content = get_file_content(filepath)
summary = model.generate_content(
f"Опиши содержание следующего текста на русском языке, укажи все нелогичные моменты и слабые места:\n\n{file_content}"
)
summary_text = (summary.candidates[0].content.parts[0].text.strip()
if summary.candidates and summary.candidates[0].content.parts
else "Нет описания.")
comments.append(f"<details><summary>Краткий пересказ для `{filepath}`</summary>\n\n{summary_text}\n</details>")
except Exception as e:
comments.append(f"<details><summary>Ошибка краткого пересказа для `{filepath}`</summary>\n\n{e}</details>")
# 1. Теги для новых файлов (status 'A')
if status == 'A':
try:
file_content = get_file_content(filepath)
tags_response = model.generate_content(
f"На основе содержания, предложи 3-5 ключевых тега на русском языке в формате '#тег'. Только теги:\n\n{file_content}"
)
tags_text = (tags_response.candidates[0].content.parts[0].text.strip()
if tags_response.candidates and tags_response.candidates[0].content.parts
else "Не удалось получить теги.")
comments.append(f"<details><summary>Теги для `{filepath}`</summary>\n\n{tags_text}\n</details>")
except Exception as e:
comments.append(f"<details><summary>Ошибка получения тегов для `{filepath}`</summary>\n\n{e}</details>")
# 2. Сравнение с design_document.md (если он существует в той же папке)
design_doc_path = os.path.join(dirname, "design_document.md")
if os.path.exists(design_doc_path):
try:
file_content = get_file_content(filepath)
result = subprocess.run(["git", "show", f"HEAD~1:{filepath}"], capture_output=True, text=True)
old_text = result.stdout if result.returncode == 0 else ""
with open(design_doc_path, 'r', encoding='utf-8') as t:
design_doc_content = t.read()
prompt_design_doc = f"""
Проанализируй следующий текст на соответствие дизайн-документу.
Укажи, чего не хватает в тексте по сравнению с дизайн-документом.
Текст файла:
{file_content}
Текст дизайн-документа:
{design_doc_content}
"""
response_design_doc = model.generate_content(prompt_design_doc)
recommendations_design_doc_text = (response_design_doc.candidates[0].content.parts[0].text.strip()
if response_design_doc.candidates and response_design_doc.candidates[0].content.parts
else "Не удалось получить рекомендации.")
comments.append(f"<details><summary>Сравнение дизайн-документа для `{filepath}`</summary>\n\n{recommendations_design_doc_text}\n</details>")
except Exception as e:
comments.append(f"<details><summary>Ошибка сравнения с дизайн-документом для `{filepath}`</summary>\n\n{e}</details>")
else:
comments.append(f"<details><summary>Сравнение дизайн-документа для `{filepath}`</summary>\n\nФайл design_document.md не найден.\n</details>")
# 3. Сравнение с README.md и TEMPLATE.md из категории
template_path = os.path.join(root_dirname, "TEMPLATE.md")
readme_path = os.path.join(root_dirname, "README.md")
try:
file_content = get_file_content(filepath)
if os.path.exists(template_path):
with open(template_path, 'r', encoding='utf-8') as t:
template_content = t.read()
else:
template_content = "Шаблон не найден."
if os.path.exists(readme_path):
with open(readme_path, 'r', encoding='utf-8') as r:
readme_content = r.read()
else:
readme_content = "README не найден."
prompt_template_readme = f"""
Проанализируй следующий текст на соответствие шаблону и README из той же папки.
Укажи, чего не хватает в тексте по сравнению с шаблоном и README.
Текст файла:
{file_content}
Текст шаблона:
{template_content}
Текст README:
{readme_content}
"""
response_template_readme = model.generate_content(prompt_template_readme)
recommendations_template_readme_text = (response_template_readme.candidates[0].content.parts[0].text.strip()
if response_template_readme.candidates and response_template_readme.candidates[0].content.parts
else "Не удалось получить рекомендации.")
comments.append(f"<details><summary>Проверка соответствия условиям категории для `{filepath}`</summary>\n\n{recommendations_template_readme_text}\n</details>")
except Exception as e:
comments.append(f"<details><summary>Ошибка проверки соответствия условиям категории для `{filepath}`</summary>\n\n{e}</details>")
# 4. Сравнение с Общими_правилами.md
rules_path = "00_Правила_оформления/Общие_правила.md"
try:
file_content = get_file_content(filepath)
with open(rules_path, 'r', encoding='utf-8') as r:
rules_content = r.read()
prompt_rules = f"""
Проанализируй следующий текст на соответствие общим правилам оформления.
Укажи, чего не хватает в тексте по сравнению с правилами.
Текст файла:
{file_content}
Общие правила:
{rules_content}
"""
response_rules = model.generate_content(prompt_rules)
recommendations_rules_text = (response_rules.candidates[0].content.parts[0].text.strip()
if response_rules.candidates and response_rules.candidates[0].content.parts
else "Не удалось получить рекомендации.")
comments.append(f"<details><summary>Проверка на соответствие общим правилам для `{filepath}`</summary>\n\n{recommendations_rules_text}\n</details>")
except Exception as e:
comments.append(f"<details><summary>Ошибка проверки на соответствие общим правилам для `{filepath}`</summary>\n\n{e}</details>")
# 5. Общее сравнение с другими статьями (все лор-файлы)
exclude_dirs = ['00_Правила_оформления', '01_Вселенная/Хронология']
all_lore_files = get_lore_files('01_Вселенная', exclude_dirs)
all_lore_files.update(get_lore_files('02_Объекты', exclude_dirs))
all_lore_files.update(get_lore_files('03_Государства', exclude_dirs))
all_lore_files.update(get_lore_files('04_Корпорации', exclude_dirs))
all_lore_files.update(get_lore_files('05_Организации', exclude_dirs))
all_lore_files.update(get_lore_files('06_Расы', exclude_dirs))
all_lore_files.update(get_lore_files('07_Существа', exclude_dirs))
all_lore_files.update(get_lore_files('08_Технологии', exclude_dirs))
all_lore_files.update(get_lore_files('09_Вооружение', exclude_dirs))
all_lore_files.update(get_lore_files('10_Явления', exclude_dirs))
all_lore_files.update(get_lore_files('11_Товары', exclude_dirs))
all_lore_files.update(get_lore_files('12_Другое', exclude_dirs))
all_lore_files.update(get_lore_files('13_Истории', exclude_dirs))
all_lore_files.update(get_lore_files('14_Повседневность', exclude_dirs))
try:
file_content = get_file_content(filepath)
context_content = ""
for path, content in all_lore_files.items():
context_content += f"\nФайл: {path}\n{content}\n"
if context_content:
prompt_all_lore = f"""
Проанализируй текст в файле `{filepath}` на предмет неточностей и противоречий относительно контекста (содержимого остальных .md файлов, кроме README.md, TEMPLATE.md и design_document.md).
Текст файла:
{file_content}
Контекст:
{context_content}
"""
response_all_lore = model.generate_content(prompt_all_lore)
recommendations_all_lore_text = (response_all_lore.candidates[0].content.parts[0].text.strip()
if response_all_lore.candidates and response_all_lore.candidates[0].content.parts
else "Не удалось получить рекомендации.")
comments.append(f"<details><summary>Общее сравнение для `{filepath}`</summary>\n\n{recommendations_all_lore_text}\n</details>")
else:
comments.append(f"<details><summary>Общее сравнение для `{filepath}`</summary>\n\nНет других статей для сравнения.\n</details>")
except Exception as e:
comments.append(f"<details><summary>Ошибка общего сравнения для `{filepath}`</summary>\n\n{e}</details>")
# 6. Сравнение с другими файлами в той же категории
exclude_dirs = ['00_Правила_оформления']
same_category_lore_files = get_lore_files(root_dirname, exclude_dirs)
try:
file_content = get_file_content(filepath)
context_category_content = ""
for path, content in same_category_lore_files.items():
if path != filepath:
context_category_content += f"\nФайл: {path}\n{content}\n"
if context_category_content:
prompt_same_category = f"""
Проанализируй текст в файле `{filepath}` на предмет неточностей относительно контекста (остальные файлы в папке, кроме README.md, TEMPLATE.md и design_document.md).
Текст файла:
{file_content}
Контекст:
{context_category_content}
"""
response_same_category = model.generate_content(prompt_same_category)
recommendations_same_category_text = (response_same_category.candidates[0].content.parts[0].text.strip()
if response_same_category.candidates and response_same_category.candidates[0].content.parts
else "Не удалось получить рекомендации.")
comments.append(f"<details><summary>Сравнение по категории для `{filepath}`</summary>\n\n{recommendations_same_category_text}\n</details>")
else:
comments.append(f"<details><summary>Сравнение по категории для `{filepath}`</summary>\n\nНет других статей в этой категории.\n</details>")
except Exception as e:
comments.append(f"<details><summary>Ошибка сравнения по категории для `{filepath}`</summary>\n\n{e}</details>")
# 7. Сравнение с предыдущей версией (если статус 'M')
try:
if status == 'M':
result = subprocess.run(["git", "show", f"HEAD~1:{filepath}"], capture_output=True, text=True)
old_text = result.stdout
file_content = get_file_content(filepath)
diff_summary = model.generate_content(
f"Кратко опиши изменения между предыдущей и текущей версиями текста.\n\nСтарый текст:\n{old_text}\n\nНовый текст:\n{file_content}"
)
diff_summary_text = (diff_summary.candidates[0].content.parts[0].text.strip()
if diff_summary.candidates and diff_summary.candidates[0].content.parts
else "Нет описания изменений.")
comments.append(f"<details><summary>Изменения в статье для `{filepath}`</summary>\n\n**Описание изменений:**\n{diff_summary_text}\n</details>")
elif status == 'A':
comments.append(f"<details><summary>Изменения в статье для `{filepath}`</summary>\n\nФайл впервые создан.\n</details>")
except Exception as e:
comments.append(f"<details><summary>Ошибка сравнения с предыдущей версией для `{filepath}`</summary>\n\n{e}</details>")
if comments:
comment_body = f"### Анализ файла `{file_name}` в папке `{root_dirname}`:\n\n" + "\n".join(comments)
create_comment(comment_body)
EOF