From cfa2dfa2484562930d989fef62de0d593eade27c Mon Sep 17 00:00:00 2001 From: Said Rabadanov Date: Sat, 6 Jun 2026 15:45:53 +0300 Subject: [PATCH] feat(content): add quality reports and CI gates --- .github/workflows/pr-check.yml | 35 +- README.md | 46 ++- package.json | 14 +- public/search-index.json | 2 +- scripts/build-search-index.mjs | 51 ++- scripts/content-quality-config.mjs | 18 + scripts/content-quality.mjs | 68 ++++ .../content-quality/__tests__/engine.test.mjs | 62 ++++ .../__tests__/html-reporter.test.mjs | 74 ++++ .../content-quality/__tests__/report.test.mjs | 70 ++++ .../content-quality/__tests__/rules.test.mjs | 197 +++++++++++ scripts/content-quality/engine.mjs | 21 ++ scripts/content-quality/html-reporter.mjs | 96 +++++ scripts/content-quality/resources.mjs | 33 ++ .../content-quality/rules/content-health.mjs | 331 ++++++++++++++++++ .../rules/content-links.mjs} | 249 ++++++++----- scripts/content-quality/rules/index.mjs | 23 ++ scripts/content-report.mjs | 123 ++++++- 18 files changed, 1368 insertions(+), 145 deletions(-) create mode 100644 scripts/content-quality-config.mjs create mode 100644 scripts/content-quality.mjs create mode 100644 scripts/content-quality/__tests__/engine.test.mjs create mode 100644 scripts/content-quality/__tests__/html-reporter.test.mjs create mode 100644 scripts/content-quality/__tests__/report.test.mjs create mode 100644 scripts/content-quality/__tests__/rules.test.mjs create mode 100644 scripts/content-quality/engine.mjs create mode 100644 scripts/content-quality/html-reporter.mjs create mode 100644 scripts/content-quality/resources.mjs create mode 100644 scripts/content-quality/rules/content-health.mjs rename scripts/{validate-content.mjs => content-quality/rules/content-links.mjs} (51%) create mode 100644 scripts/content-quality/rules/index.mjs diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index c0b38ee..3d43356 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -12,14 +12,12 @@ concurrency: cancel-in-progress: true jobs: - quality: + content-quality: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 @@ -30,18 +28,37 @@ jobs: - name: Install dependencies run: npm ci - - name: Prepare generated content - run: npm run prepare:content + - name: Test content quality rules + run: npm run test:content + + - name: Run content quality gates + run: npm run content:quality - name: Upload content report if: always() uses: actions/upload-artifact@v4 with: - name: content-report-json - path: .cache/content-report.json + name: content-quality-report + if-no-files-found: warn + path: | + .cache/content-report.json + .cache/content-report.html + + application-quality: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm - - name: Validate markdown links and content references - run: npm run validate:content + - name: Install dependencies + run: npm ci - name: Lint run: npm run lint diff --git a/README.md b/README.md index 140cc34..7de5d88 100644 --- a/README.md +++ b/README.md @@ -97,10 +97,46 @@ npm run dev - `npm run prepare:content:warning` - сборка manifest в warning-режиме без падения по content issues. - `npm run prepare:content:report` - генерация `content-report.json` с autofix suggestions. - `npm run content:sync` - compatibility alias для `content:manifest`. +- `npm run content:quality` - сборка manifest/search index, запуск quality gates и создание JSON/HTML-отчетов. +- `npm run test:content` - unit-тесты движка, правил и генераторов отчетов. +- `npm run quality` - подготовка контента и полный запуск content quality gates. - `npm run search:build` - генерация `public/search-index.json`. -- `npm run validate:content` - проверка markdown-ссылок, якорей и репозиторных ссылок в code fence. +- `npm run validate:content` - compatibility alias для `content:quality`. - `npm run export` - информационный скрипт: static export выполняется внутри `next build`. +## Критерии качества контента + +Полная локальная проверка контента: + +```bash +npm run quality +npm run test:content +``` + +Отчеты сохраняются в: + +- `.cache/content-report.json` - структурированный отчет для автоматической обработки; +- `.cache/content-report.html` - человекочитаемый отчет с метриками и найденными проблемами. + +Ошибки блокируют CI: + +- отсутствующий `description`; +- дублирующийся slug; +- некорректный author; +- битая внутренняя ссылка, anchor или ссылка на файл; +- пустой section index; +- страница крупнее 1 MiB; +- search chunk длиннее 420 символов. + +Предупреждения не блокируют CI: + +- страница крупнее 512 KiB; +- неиспользуемый файл из `resources/`; +- orphan page, на которую не ссылаются другие документы. + +Пороговые значения и исключения находятся в +[`scripts/content-quality-config.mjs`](./scripts/content-quality-config.mjs). + ## Структура проекта ```text @@ -122,7 +158,7 @@ SUPPORT.md ## CI/CD и деплой -- [`.github/workflows/pr-check.yml`](./.github/workflows/pr-check.yml) проверяет `prepare:content`, `validate:content`, `lint`, `typecheck` и `build` для Pull Request. +- [`.github/workflows/pr-check.yml`](./.github/workflows/pr-check.yml) проверяет content quality rules, публикует JSON/HTML-отчет как artifact, а также запускает `lint`, `typecheck` и `build` для Pull Request. - [`.github/workflows/deploy-gh-pages.yml`](./.github/workflows/deploy-gh-pages.yml) публикует сайт в GitHub Pages при пуше в `main` и при ручном запуске. - В `Settings -> Pages` должен быть выбран `Source: GitHub Actions`. @@ -138,9 +174,9 @@ SUPPORT.md ## Как вносить изменения 1. Добавьте или обновите материал в `docs//...`. -2. Запустите `npm run content:manifest` или сразу `npm run prepare:content`. -3. Проверьте контент командой `npm run validate:content`. -4. Проверьте проект командами `npm run lint` и `npm run typecheck`. +2. Запустите `npm run quality`. +3. Запустите `npm run test:content`. +4. Проверьте проект командами `npm run lint`, `npm run typecheck` и `npm run build`. 5. Откройте Pull Request. Для каждого материала требуется поле `author` во frontmatter: GitHub login или ссылка на профиль. diff --git a/package.json b/package.json index 735731f..ec49dcd 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,13 @@ "private": true, "description": "Static-first documentation platform for StackMIREA.", "scripts": { + "prepare:content": "npm run content:manifest && npm run search:build", + "content:manifest": "node scripts/content-manifest.mjs --mode=error", + "search:build": "node scripts/build-search-index.mjs", + "content:quality": "node scripts/content-quality.mjs", + "validate:content": "npm run content:quality", + "test:content": "node --test scripts/content-quality/__tests__/*.test.mjs", + "quality": "npm run content:quality", "dev": "next dev", "predev": "npm run prepare:content", "build": "next build", @@ -11,13 +18,9 @@ "start": "npx serve out -p 3000", "lint": "next lint", "typecheck": "tsc --noEmit --incremental false", - "prepare:content": "npm run content:manifest && npm run search:build", "prepare:content:warning": "node scripts/content-manifest.mjs --mode=warning && npm run search:build", "prepare:content:report": "node scripts/content-manifest.mjs --mode=autofix-report", - "content:manifest": "node scripts/content-manifest.mjs --mode=error", "content:sync": "npm run content:manifest", - "search:build": "node scripts/build-search-index.mjs", - "validate:content": "node scripts/validate-content.mjs", "prebuild": "npm run prepare:content" }, "dependencies": { @@ -53,7 +56,4 @@ "engines": { "node": ">=20.0.0" } - - } - diff --git a/public/search-index.json b/public/search-index.json index b31b609..c8b4ae7 100644 --- a/public/search-index.json +++ b/public/search-index.json @@ -1 +1 @@ -{"version":1,"generatedAt":"2026-05-05T08:47:47.095Z","docs":[{"id":"ai","href":"/docs/ai","slug":["ai"],"section":"ai","sectionTitle":"AI","title":"AI","description":"Рабочие тетради по искусственному интеллекту в формате MDX.","preview":"AI обзор Раздел объединяет 8 рабочих тетрадей по дисциплине «Искусственный интеллект», перенесенных в MDX формат. Материалы идут от базового Python и научных библиотек к классическим ML методам, нейросетям, эволюционным алгоритмам и кластеризации. Что внутри P","keywords":["notebook","python","ai","ml","данных","деревья","методы","раздел","решений","knn","mdx","numpy","pandas","алгоритмам","базового","библиотек","внутри","генетические"],"topics":["python","ai","knn","pandas","numpy","oop","algorithms","clustering","regression"],"chunks":[{"id":"chunk-0","heading":"AI обзор","text":"Раздел объединяет 8 рабочих тетрадей по дисциплине «Искусственный интеллект», перенесенных в MDX формат. Материалы идут от базового Python и научных библиотек к классическим ML методам, нейросетям, эволюционным алгоритмам и кластеризации.","keywords":["ai","mdx","mdx.","ml","python","алгоритмам","базового","библиотек","дисциплине","идут","интеллект","интеллекту"],"topics":["python","ai","oop","algorithms"]},{"id":"chunk-1","heading":"Что внутри","text":"Python, NumPy и pandas для подготовки данных; метрики расстояния, KNN и базовые техники классификации; регрессия и деревья решений; эволюционные методы, нейросети и кластеризация.","keywords":["ai","knn","mdx.","numpy","pandas","python","базовые","внутри","данных","деревья","интеллекту","искусственному"],"topics":["python","knn","pandas","numpy","oop","clustering","regression"]},{"id":"chunk-2","heading":"Ноутбуки","text":"Notebook 1 — Основа Python типы данных, условия, циклы и вводные примеры. Notebook 2 — NumPy и pandas массивы, таблицы и базовая подготовка данных. Notebook 3 — Метрики и KNN расстояния между объектами и классификация ближайших соседей. Notebook 4 — Регрессия линейные модели, аппроксимация и оценка качества. Notebook 5 — Деревья решений деревья решений и работа с классификаторами","keywords":["notebook","ai","деревья","решений","knn","mdx.","numpy","pandas","python","аппроксимация","базовая","ближайших"],"topics":["python","knn","pandas","numpy","oop","regression"]},{"id":"chunk-3","heading":"Ноутбуки","text":". Notebook 6 — Генетические и эволюционные методы оптимизация, генетические алгоритмы и отжиг. Notebook 7 — Нейронные сети персептрон, MLP и основы обучения сети. Notebook 8 — Кластеризация методы группировки данных без учителя.","keywords":["ai","notebook","генетические","методы","mdx.","mlp","алгоритмы","без","группировки","данных","интеллекту","искусственному"],"topics":["ai","algorithms","clustering"]},{"id":"chunk-4","heading":"Как читать раздел","text":"Начните с Notebook 1 и Notebook 2 , если нужно выровнять базу по Python и обработке данных. Для классического ML переходите к Notebook 3 , Notebook 4 и Notebook 5 . Темы оптимизации и более продвинутых подходов собраны в Notebook 6 , Notebook 7 и Notebook 8 .","keywords":["notebook","ai","mdx.","ml","python","базу","более","выровнять","данных.","если","интеллекту","искусственному"],"topics":["python","ai","oop"]}]},{"id":"ai/notebook-01-python-basics","href":"/docs/ai/notebook-01-python-basics","slug":["ai","notebook-01-python-basics"],"section":"ai","sectionTitle":"AI","title":"AI Notebook 1 — Основа Python","description":"Типы данных, условия, циклы и старт работы с NumPy.","preview":"Основа Python. Библиотеки. Дата 25.02.2023 1.1. Теоретический материал – Типы данных Типы данных Все типы данных в Python относятся к одной из 2 х категорий: изменяемые (mutable) и неизменяемые (immutable). Неизменяемые объекты: исловые данные (int, float), bo","keywords":["print","python","type","задача","10","elif","dev","от","plt.plot","range","данных","apple","df","if","import","rng","trapz","массива"],"topics":["python","ai","numpy","knn"],"chunks":[{"id":"chunk-0","heading":"","text":"Типы данных Все типы данных в Python относятся к одной из 2 х категорий: изменяемые (mutable) и неизменяемые (immutable). Неизменяемые объекты: исловые данные (int, float), bool, None, символьные строки (class 'str'), кортежи (tuple). Изменяемые объекты: списки (list), множества (set), словари (dict).","keywords":["ai","данных","типы","python","изменяемые","неизменяемые","объекты","bool","class","dict","float","immutable"],"topics":["python"]},{"id":"chunk-1","heading":"","text":"python x= 3+5.2 7 y= None z= 'a',5,12.345, (2,'b') df= [['Антонова Антонина',34,'ж'],['Борисов Борис',26,'м']] A={1,'title',2,'content'} print(x,' ',type(x),'\\n',y,' ',type(y),'\\n',df,' ',type(df),'\\n',A,' ',type(A),'\\n')","keywords":["type","ai","df","python","12.345","26","3+5.2","34","content","none","notebook","numpy."],"topics":["python"]},{"id":"chunk-2","heading":"","text":"code 39.4 None [['Антонова Антонина', 34, 'ж'], ['Борисов Борис', 26, 'м']] {'content', 1, 2, 'title'}","keywords":["ai","26","34","39.4","code","content","none","notebook","numpy.","python","title","антонина"],"topics":[]},{"id":"chunk-3","heading":"","text":"python x=5 =2 A={ 1,3,7,8} B={2,4,5,10,'apple'} C=A&B df= 'Антонова Антонина',34,'ж' z='type' D=[1,'title',2,'connect'] print(x,' ',type(x),'\\n',A,' ',type(A),'\\n',B,' ',type(B),'\\n',C,' ',type(C),'\\n',df,' ',type(df),'\\n',z,' ',type(z),'\\n',D,' ',type(D),'\\n')","keywords":["type","ai","df","python","10","34","apple","connect","notebook","numpy.","print","title"],"topics":["python"]},{"id":"chunk-4","heading":"","text":"text True {8, 1, 3, 7} {2, 'apple', 4, 5, 10} set() ('Антонова Антонина', 34, 'ж') type [1, 'title', 2, 'connect']","keywords":["ai","10","34","apple","connect","notebook","numpy.","python","set","text","title","true"],"topics":[]},{"id":"chunk-5","heading":"","text":"В коде часто приходится проверять выполнимость или невыполнимость каких то условий. Синтаксис:","keywords":["ai","notebook","numpy.","python","выполнимость","данных","каких","коде","невыполнимость","основа","приходится","проверять"],"topics":[]},{"id":"chunk-6","heading":"","text":"Обратите внимание, что код, который должен выполняться внутри каждого условия, записывается с отступом в 4 пробела от уровня if, elif и else: в питоне области видимости переменных обозначаются отступами.","keywords":["ai","условия","elif","else","if","notebook","numpy.","python","видимости","внимание","внутри","выполняться"],"topics":["python"]},{"id":"chunk-7","heading":"","text":"То есть, отступы позволяют понять, где начинается код, который должен выполняться при выполнении условия в if, и где заканчивается.","keywords":["ai","условия","if","notebook","numpy.","python","выполнении","выполняться","данных","должен","заканчивается.","код"],"topics":[]},{"id":"chunk-8","heading":"","text":"Задача:Вывести на экран является ли переменная х положительной, отрицательной или равна нулю.","keywords":["ai","notebook","numpy.","python","вывести","данных","задача","ли","нулю.","основа","отрицательной","переменная"],"topics":[]},{"id":"chunk-9","heading":"","text":"python x=125 if x<0: print('x отрицательный') elif x==0: print('x равен 0') else: print('x положительный')","keywords":["ai","print","python","125","elif","else","if","notebook","numpy.","данных","основа","отрицательный"],"topics":["python"]},{"id":"chunk-10","heading":"","text":"Задача: Напишите код. Задается х, напечатать какому из интервалов принадлежит: ( infinity, 5), [ 5, 5] или от (5, +infinity)","keywords":["ai","+infinity","infinity","notebook","numpy.","python","данных","задается","задача","интервалов","какому","код."],"topics":[]},{"id":"chunk-11","heading":"","text":"python x=int(input()) if x < 5: print('x пренадлежит интервалу от бесконечности до 5') elif 5