chore(release): 0.2.0 #13

Merged
ksv741 merged 5 commits from release/containerized-delivery into main 2026-09-16 11:50:36 +03:00
15 changed files with 89 additions and 358 deletions
Showing only changes of commit 95e4361599 - Show all commits
+7 -10
View File
@@ -9,9 +9,8 @@
3. Расширь подходящий owner/helper/pattern; выбирай минимальное решение,
полностью закрывающее текущую задачу. Новый файл сам по себе не улучшение.
Уменьшай количество способов решать одну техническую задачу. Во frontend
сравни query keys, formatters, class composition; в backend — pagination,
error mapping, repositories и обращения к одному provider. Сходный синтаксис
Уменьшай количество способов решать одну техническую задачу: конфигурацию
release-it и commitlint поддерживай средствами этих инструментов. Сходный синтаксис
без общей ответственности не требует DRY. Критерий dependency — в
[dependencies](dependencies.md).
@@ -23,10 +22,8 @@ Consistency с подходящим project pattern предпочтительн
Сначала используй native API/установленный stack; не строй на гипотетических
consumers и будущих requirements.
Backend: CQRS, Event Sourcing, Domain Events, Unit of Work, Command Bus,
generic repositories, abstract factories, custom DI и elaborate domain layers
не вводятся только ради «best practice». Frontend: wrapper над Mantine, global
store и дополнительная OpenAPI hash/schema система не создаются автоматически.
Не вводи shared tooling для других репозиториев как обязательную зависимость
их сборки, CI или маршрута инструкций. Каждый repo владеет своими checks.
Trust-boundary validation, authorization, atomicity, accessibility и необходимая
regression proof остаются частью полного решения.
@@ -44,8 +41,8 @@ low-risk, behavior-preserving проблемы, которые легко про
Устойчивый pattern должен находиться через код и manifest. Добавляй convention,
только если следующей сессии всё ещё нужно явное project decision rule.
У каждого правила один owner; AGENTS содержит trigger и ссылку.
Mental model живёт в `.ai/architecture.md`, intent — в specs, системные знания —
в docs. Audit/backlog не помещай в conventions.
Устройство root tooling находится в README и конфигурации; их чтение не
требует соседних checkout. Audit/backlog не помещай в conventions.
Legacy фиксируется отдельно; новый diff не добавляет нарушений. При migration
закрывай подтверждённые baseline findings с evidence, сохраняя область проверки.
@@ -65,4 +62,4 @@ Numeric policy называй по смыслу и единицам (`TIMEOUT_MS
Индексы, `index + 1`, `Math.max(value, 0)` и обычные labels/log messages
не выноси в constants без semantic benefit. Локальный lint/i18n profile всё равно
применим. Не создавай tests существования констант или custom magic-string analyzer.
Различия TS/генератора принадлежат local conventions frontend/backend.
Правила приложений принадлежат их локальным conventions.
+2 -6
View File
@@ -14,9 +14,6 @@ native API и зрелую небольшую dependency: parsing, serialization
debounce, class composition. Три `Array.map` не требуют utility library.
Не продолжай копировать boilerplate, если один принятый способ дешевле сопровождать.
`clsx` сейчас не принят как direct dependency. Разные способы соединить классы
сначала требуют подтверждённого повторения; устанавливать его заранее не нужно.
## При выборе пакета
- Укажи конкретную потребность, альтернативу из текущего stack и существенную
@@ -26,8 +23,7 @@ debounce, class composition. Три `Array.map` не требуют utility libr
- Сохраняй exact versions и lockfile. Перед Node-командами активируй `.nvmrc`
через `nvm use --silent`; воспроизводимая установка — `npm ci` в нужном repo.
- Peer-конфликты решай совместимыми версиями, без `--force`/`--legacy-peer-deps`.
Известная frontend граница TypeScript/openapi-typescript — в
[README](../../frontend/README.md#проверка); версии принадлежат manifest/lockfile.
Версии и compatibility принадлежат местному manifest/lockfile.
- После изменения запускай relevant install/type/build/behavior checks и
existing security workflow. Не добавляй второй analyzer, когда задача решается
`npm audit` при изменении зависимостей. Не добавляй второй analyzer, когда задача решается
настройкой существующего; [карта проверок](guardrails.md).
+3 -5
View File
@@ -7,16 +7,14 @@
- Commit содержит одну связанную задачу и её необходимые проверки/документацию.
Межрепозиторное изменение оформляй отдельными commits/PR с взаимными ссылками.
- Commits и заголовки PR — Conventional Commits; допустим `spec:`.
Во frontend номер связанной issue добавляй в конец заголовка (`fix: … #12`);
без связанной issue номер не нужен. Формат проверяет local commitlint.
Формат проверяет local commitlint; правила других repo принадлежат их AGENTS.
- Перед commit проверь `git diff --cached --name-status` и сам staged diff
в каждом affected repo. Root tracking ограничен [.gitignore](../../.gitignore):
новый общий файл получает точечный allowlist и проверку
[tracking](../../test-workspace-tracking.py). `git add -f` не используется.
новый общий файл получает точечный allowlist; проверь staged paths вручную. `git add -f` не используется.
- Не выполняй `git clean -fdx`, reset чужих изменений, force-push, удаление или
перенос опубликованных тегов как обычную очистку. Для опасной операции нужны
явная необходимость, сохранение данных и соответствующая авторизация.
- Обычный PR допускает squash. Release/hotfix, синхронизация main/develop,
release-конфигурация и CI refs требуют чтения
release-конфигурация требуют чтения
[процесса релиза](../../README.md#релизы-и-ветки): он владеет merge policy,
версиями и тегами. Прямой push в `main` запрещён; версии вручную не повышай.
+12 -45
View File
@@ -1,48 +1,15 @@
# Conventions → deterministic guardrails
# Root проверки
Читай при выборе проверки или изменении tooling. Команды выполняются из repo
владельца после `nvm use --silent`. Config/manifest задаёт фактический scope;
таблица не заменяет semantic review и не утверждает, что все checks уже запущены.
Root CI содержит только `commits`: штатный commitlint проверяет заголовок PR
и коммиты между BASE и HEAD. Установка — `npm ci --ignore-scripts`, Node — из
местной `.nvmrc`. Самописные проверки и дополнительные CI workflow не добавляются.
| Convention | Существующий check | Достаточность / следующий шаг |
| --- | --- | --- |
| Git multi-repo tracking | root `python3 test-workspace-tracking.py`, `.gitignore` | Проверяет exact allowlist и сохранность fictional data/nested Git; обновлять вместе с allowlist |
| Conventional Commits | local commitlint + commits workflow | Syntax покрыт; атомарность и scope — review |
| Dependencies | exact manifest/lockfile, `npm ci`, security workflows (audit/Gitleaks) | Reuse, peer compatibility, ценность dependency — review; новый analyzer не нужен |
| Frontend types/style/literals | TypeScript strict, `erasableSyntaxOnly`; Biome `npm run lint` | Props profile, i18n и symbolic strings требуют review; literals exclusions — в Biome |
| Frontend FSD | dependency-cruiser `npm run deps` | Upward imports, cycles, public entry points покрыты; same-layer запрет неполный, semantic ownership не проверяется |
| Unused frontend code/deps | `npm run knip`, TS unused locals/params | Existing tool достаточно; не добавлять второй unused analyzer |
| API generation | frontend `openapi:check`; backend `test:openapi-contract` | Первый сравнивает generated types со snapshot; второй — Nest schema со snapshot. Ни один не доказывает runtime validation errors |
| Query/auth/error/finance behavior | Vitest + RTL/MSW; backend Jest/SQLite/HTTP | Test Intent выбирает риск и уровень, без автоматического теста для каждого файла |
| Storybook | `typecheck`, `deps:storybook`, `build:catalog` | Imports/types/build покрыты; `play`, visual/keyboard/browser behavior проверять отдельно |
| Backend types/style | ESLint/Prettier, app/test TypeScript | Zero-warning gate; `no-explicit-any` отключён, часть unsafe rules включена. Не обещать полного запрета `any` |
| Backend modules/domain | TypeScript и semantic review | Отдельного architecture analyzer нет. При повторных нарушениях сначала рассмотреть existing ESLint `no-restricted-imports`, без нового custom analyzer |
| DB ownership/atomicity | Prisma schema/constraints, `test:sqlite`, `test:e2e`, `test:durability` | Mock Prisma недостаточно. Relevant suites на disposable DB, без reset пользовательской базы |
| Logging/security | Logging/auth/access tests, configured HTTP tests, Gitleaks | Allowlist/redaction имеют границы. Встроенный ESLint `no-console` можно включить отдельно; semantic security review обязателен |
| Markdown/routing | docs markdownlint-cli2 + relative-links, `git diff --check` | Existing docs gate охватывает docs, а не соседние `.ai`; для них запустить тот же установленный CLI с явным списком путей и docs config |
Локально перед Node-командами выполни `nvm use --silent`. Для изменений проверь
JSON/config syntax, тексты, ссылки и `git diff --check`; новые файлы просмотри
отдельно. Перед commit проверь staged paths относительно allowlist `.gitignore`.
Зависимости устанавливай через `npm ci`; при их изменении выполни `npm audit`.
## Ratchet без новой инфраструктуры
- Existing error gates остаются обязательными; не ослабляй их ради legacy.
- До расширения FSD rule получи candidate violations существующим depcruise.
Его `--ignore-known` допускает native baseline; сначала проверь формат через
`--help`. Не игнорируй целые directories. Обновление baseline принимается
только с evidence закрытия/явным обоснованием нового исключения.
- Semantic findings учитываются stable IDs в
[inventory](../migrations/conventions-v1/audit.md) и закрываются vertical slices
по [plan](../migrations/conventions-v1/plan.md). Это reviewed inventory,
не автоматический полный счётчик нарушений.
- Новый analyzer/custom baseline CI не создаётся. Если существующей проверки
недостаточно, сначала сформулируй повторяющийся regression и стоимость
поддержки предлагаемого rule.
## Выбор финального gate
Markdown/routing-only: проверить все новые/изменённые Markdown, ссылки/якоря,
достижимость тем, смысл и `git diff --check`. Root allowlist change дополнительно
требует tracking-тест. Не запускать application builds/coverage только ради текста.
Code/dependency changes: full `npm run verify` affected repo после lockfile install;
targeted tests ускоряют итерацию, но не подменяют required gate. Wire changes
дополнительно требуют backend contract check, HTTP/DB changes — relevant isolated
suites по local Testing. Форматирующие `lint:fix`/`format` — mutations, не proof.
Release-it использует встроенные проверки ветки, чистого дерева и наличия
коммитов. Перед подготовкой релиза вручную проверь теги и соответствие версии
последнему принятому тегу; повторно не повышай уже подготовленную версию.
Процесс — в [README](../../README.md#релизы-и-ветки).
+2 -2
View File
@@ -22,5 +22,5 @@ jobs:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
printf '%s\n' "$PR_TITLE" | npm run commitlint -- --strict
npm run commitlint -- --strict --from "$BASE_SHA" --to "$HEAD_SHA"
printf '%s\n' "$PR_TITLE" | npm exec --no -- commitlint --strict
npm exec --no -- commitlint --strict --from "$BASE_SHA" --to "$HEAD_SHA"
-31
View File
@@ -1,31 +0,0 @@
name: Workspace security
on:
push:
branches:
- '**'
pull_request:
schedule:
- cron: '17 5 * * *'
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
with:
fetch-depth: 0
- name: Record checked revision
run: git rev-parse HEAD
- name: Scan history with Gitleaks 8.30.1
run: |
curl --fail --location --silent --show-error https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz -o /tmp/gitleaks.tar.gz
echo '551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz' | sha256sum --check
mkdir -p /tmp/gitleaks-bin
tar -xzf /tmp/gitleaks.tar.gz -C /tmp/gitleaks-bin gitleaks
/tmp/gitleaks-bin/gitleaks git --log-opts=--all --redact=100 --no-banner
- uses: https://github.com/actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Audit runtime and development dependencies
run: npm audit --include=dev --audit-level=low
-17
View File
@@ -1,17 +0,0 @@
name: Workspace verify
on:
push:
branches: ['**']
pull_request:
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
- uses: https://github.com/actions/setup-node@v4
with:
node-version-file: .nvmrc
- run: npm ci
- run: npm run verify
-7
View File
@@ -3,7 +3,6 @@
!/.gitignore
!/AGENTS.md
!/README.md
!/test-workspace-tracking.py
!/.ai/
/.ai/*
!/.ai/conventions/
@@ -26,14 +25,8 @@
!/CHANGELOG.md
!/commitlint.config.cjs
!/renovate.json
!/test-release.cjs
!/scripts/
/scripts/*
!/scripts/check-release.cjs
!/.gitea/
/.gitea/*
!/.gitea/workflows/
/.gitea/workflows/*
!/.gitea/workflows/commits.yml
!/.gitea/workflows/verify.yml
!/.gitea/workflows/security.yml
-3
View File
@@ -18,9 +18,6 @@
"gitlab": {
"release": false
},
"hooks": {
"after:git:init": "node scripts/check-release.cjs"
},
"plugins": {
"@release-it/conventional-changelog": {
"infile": "CHANGELOG.md",
+23 -22
View File
@@ -1,31 +1,32 @@
# WealthLedger workspace
`frontend`, `backend`, `docs`, `specs` и root — независимые Git-репозитории.
Root содержит workspace-навигацию и конфигурацию стандартных инструментов.
Самописные скрипты не добавляются; CI выполняет только commitlint.
`frontend`, `backend`, `docs`, `specs` — самостоятельные Git-репозитории.
Root tracking ограничен allowlist; локальные данные и nested repos в него
не добавляются. `references/**`, dated audits и архивы — read-only.
Структура/запуск — в [README](README.md).
Начни с результата задачи и affected areas. Прочитай local AGENTS затронутого
repo; проследи поведение/инварианты и найди owner с 1–3 примерами до реализации.
Код показывает механизм, а случайный аналог не переопределяет контракт.
Работай напрямую; OpenSpec/SDD lifecycle нужен только по явному запросу.
Инструкции root относятся только к root. Работа внутри другого репозитория
начинается с его собственного AGENTS; он содержит все обязательные правила
без наследования root conventions и без чтения соседних checkout.
Публичные API artifacts передаются явно; CI каждого repo использует локальные
входы. Справочные источники не становятся обязательной цепочкой инструкций.
| Задача / граница | Прочитать |
Начни с результата и affected areas. До edits проследи поведение/invariants,
найди owner и 1–3 примера. Работай напрямую; OpenSpec/SDD — по явному запросу.
| Root задача | Прочитать |
| --- | --- |
| Frontend / backend | [Frontend](frontend/AGENTS.md) / [Backend](backend/AGENTS.md), затем только активированные темы |
| Docs / specs | [Docs AGENTS](docs/AGENTS.md) / [Specs AGENTS](specs/AGENTS.md) |
| Новая функция, неясное ожидание, межрепозиторное поведение | [Docs entry](docs/README.md) → нужная область/spec/ADR; локальный build/lint bug начинает с repo instructions/code |
| Новый helper/hook/wrapper/pattern, refactor, выбор scope | [Code health / change scope](.ai/conventions/code-health.md) |
| Dependency или tooling | [Dependencies](.ai/conventions/dependencies.md), [guardrails](.ai/conventions/guardrails.md) |
| Нетривиальные tests | [Test Intent](.ai/conventions/testing.md) и local Testing |
| Git, ветка, commit, PR, tracking | [Git](.ai/conventions/git.md) |
| Release/hotfix, main↔develop, release config/CI refs | [Release process](README.md#релизы-и-ветки) |
| Migration существующих conventions findings | [Audit/plan](.ai/migrations/conventions-v1/plan.md) |
| Helper/pattern, refactor, scope | [Code health](.ai/conventions/code-health.md) |
| Dependency/tooling | [Dependencies](.ai/conventions/dependencies.md), [Guardrails](.ai/conventions/guardrails.md) |
| Нетривиальные tests | [Test Intent](.ai/conventions/testing.md) |
| Git/branch/commit/PR/tracking | [Git](.ai/conventions/git.md) |
| Release/hotfix | [README](README.md#релизы-и-ветки) |
Перед Node-командой активируй `.nvmrc` affected repo через `nvm use --silent`.
Перед Node-командой активируй местную `.nvmrc`: `nvm use --silent`.
При новой границе и перед завершением повтори routing по staged/unstaged/new
files; сохраняй чужие изменения. Missing required source или конфликт требований
сообщи до зависимого решения. После code diff выполни
[Boy Scout review](.ai/conventions/code-health.md#boy-scout-review); проверь результат
по рискам и [relevant gates](.ai/conventions/guardrails.md#выбор-финального-gate).
Сохрани краткие команды/результаты и ограничения; успех lint не заменяет semantic review.
files; сохраняй чужие изменения. Missing source/конфликт требований сообщи
до зависимого решения. После diff выполни
[Boy Scout review](.ai/conventions/code-health.md#boy-scout-review) и
[relevant gates](.ai/conventions/guardrails.md). Запиши краткие команды,
результаты и ограничения; lint не заменяет semantic review.
+40 -36
View File
@@ -2,7 +2,7 @@
Локальное приложение для портфеля T-Bank, операций, дохода и будущих выплат,
а также ручного учёта банковских карт и расходов.
[Обзор продукта](docs/overview.md) и [документация системы](docs/README.md).
[Обзор продукта](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/overview.md) и [документация системы](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/README.md).
## Структура workspace
@@ -12,10 +12,10 @@ Workspace состоит из пяти независимых Git-репозит
| Путь от root | Репозиторий Gitea | Назначение |
| --- | --- | --- |
| `.` | `WealthLedger/main` | Вход в workspace, инструкции и правила root tracking |
| `frontend/` | `WealthLedger/frontend` | [React/Vite UI](frontend/README.md) |
| `backend/` | `WealthLedger/backend` | [NestJS API, Prisma и SQLite](backend/README.md) |
| `docs/` | `WealthLedger/docs` | [Объяснение системы](docs/README.md) |
| `specs/` | `WealthLedger/specs` | [Требования и OpenSpec](specs/README.md) |
| `frontend/` | `WealthLedger/frontend` | [React/Vite UI](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md) |
| `backend/` | `WealthLedger/backend` | [NestJS API, Prisma и SQLite](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md) |
| `docs/` | `WealthLedger/docs` | [Объяснение системы](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/README.md) |
| `specs/` | `WealthLedger/specs` | [Требования и OpenSpec](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/README.md) |
### Получение checkout
@@ -34,13 +34,14 @@ git clone ssh://git@git.personal.ksv741.ru:2222/WealthLedger/specs.git specs
Для существующего workspace клонируйте только отсутствующие checkout в пустые
каталоги. Проверяйте состояние каждого repo отдельно, например
`git -C frontend status`. Обновление main не обновляет соседние repos;
общего lockfile revisions нет. Относительные ссылки требуют указанных имён каталогов.
общего lockfile revisions нет. Такая раскладка удобна для совместной работы,
но каждый checkout можно использовать отдельно.
### Что хранится в main
Root отслеживает входные документы, общие engineering conventions и их migration
Root отслеживает входные документы, собственные engineering conventions и исторический migration
inventory в `.ai/`, npm/release-конфигурацию, changelog,
проверки tracking/release и три CI workflow. Точный allowlist — в `.gitignore`.
один CI workflow с commitlint. Точный allowlist — в `.gitignore`.
Остальные root-пути игнорируются по умолчанию:
вложенные repos, `.env`, базы и WAL, импорты выписок, PDF, `references/`,
`design/`, `todo/` и локальные настройки инструментов.
@@ -61,7 +62,7 @@ inventory в `.ai/`, npm/release-конфигурацию, changelog,
После смены major Node переустановите native dependencies через `npm ci`.
Разрешения install scripts заданы в `package.json` соответствующего repo.
Для нового backend создайте `.env` по [backend README](backend/README.md),
Для нового backend создайте `.env` по [backend README](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md),
замените оба placeholder-секрета и проверьте `DATABASE_URL`.
Существующий `.env` сохраняйте. Из backend:
@@ -74,13 +75,13 @@ npm run start:dev
Build генерирует Prisma client. Перед миграцией существующей базы сохраните
согласованный backup и ключ шифрования; процедуру и ограничения отката смотрите
в [описании хранения](docs/architecture/containers.md#сохранение-и-восстановление-данных).
в [описании хранения](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/architecture/containers.md#сохранение-и-восстановление-данных).
Известная ошибка fresh migration при `RUST_LOG=warn/error` устранена в
`backend/prisma.config.ts`: CLI удаляет унаследованную переменную.
Используйте штатный `prisma migrate deploy` из backend; прежний Prisma blocker
снят. [Подробности и границы проверки](backend/README.md#реальная-sqlite-и-сохранность-данных).
снят. [Подробности и границы проверки](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md#реальная-sqlite-и-сохранность-данных).
В другом терминале подготовьте `.env` по [frontend README](frontend/README.md),
В другом терминале подготовьте `.env` по [frontend README](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md),
затем из frontend:
```bash
@@ -99,7 +100,7 @@ npm run dev
OpenSpec не нужен для запуска приложения и применяется по явному запросу.
Specs использует Node из своей `.nvmrc`; проверенная версия CLI — 1.10.0.
Store `wealth-ledger-specs` расположен в `specs/openspec/`.
[Работа с OpenSpec](specs/AGENTS.md), [команды validation](specs/README.md#проверка).
[Работа с OpenSpec](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/AGENTS.md), [команды validation](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/README.md#проверка).
## Проверки
@@ -107,17 +108,18 @@ Store `wealth-ledger-specs` расположен в `specs/openspec/`.
| Где | Команда | Что проверяет |
| --- | --- | --- |
| root | `python3 test-workspace-tracking.py` | Массовый add на вымышленных данных; сохранность файлов и вложенных Git |
| root | `npm exec --no -- commitlint --strict --from <base> --to <head>` | Формат сообщений коммитов |
| root | `git diff --check` | Whitespace в изменениях root |
| frontend | `npm run verify` | Типы, lint, тесты, зависимости, OpenAPI snapshot и сборки |
| backend | `npm run verify` | Lint, типы, unit tests и build |
| backend | `npm run test:e2e` | HTTP/SQLite suites и контракт с соседним frontend |
| backend | `npm run test:e2e` | HTTP/SQLite suites и локальный OpenAPI artifact |
| docs | `npm run verify` | Markdown, относительные ссылки и diff |
Python 3 нужен только для проверки root tracking. Backend e2e использует
Root CI выполняет только commitlint: проверяет сообщения коммитов и заголовок PR.
Allowlist root проверяется вручную по staged paths. Backend e2e использует
временные базы; verify отдельно не доказывает HTTP/DB-сценарии или доступность
T-Bank. Полные команды и ограничения принадлежат README
[frontend](frontend/README.md#проверка) и [backend](backend/README.md#проверка).
[frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md#проверка) и [backend](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md#проверка).
Для изменения проекта начните с [AGENTS](AGENTS.md).
## Релизы и ветки
@@ -144,18 +146,19 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
1. Обновить `main`, `develop` и теги командой `git fetch origin --tags`.
Проверить, что текущий `develop` содержит последний релиз из `main`.
Создать `release/<name>` от проверенного `develop` и выполнить `npm ci`.
2. Для backend и docs обновить `.ci/refs.json`: полные SHA совместимых соседних
репозиториев. Эти revisions должны быть доступны в Gitea. Проверить именно
их совместимость; движущиеся ветки не являются фиксацией состава релиза.
Закоммитить обновлённые pins до подготовки версии.
2. Проверить локальные входы и публичные artifacts выпускаемого репозитория.
Совместимость развёрнутых участников согласовать отдельно через публичные
контракты; CI не скачивает соседние checkout и не требует их SHA.
3. Выполнить `npm run release:preview`. Проверить номер и changelog, затем
`npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`, обновляет
`package.json`, lockfile и `CHANGELOG.md`; теги и push не выполняет.
Не использовать обход branch/clean/tag guards. Незакоммиченные файлы,
неподходящая ветка, отсутствие изменений и уже подготовленная версия блокируют выпуск.
В root встроенные проверки release-it контролируют ветку, чистое дерево и
наличие коммитов. Теги и соответствие версии последнему принятому тегу
проверяются вручную; уже подготовленную версию повторно не повышать.
4. Проверить diff релизного коммита и штатные gates. Отправить только релизную
ветку: `git push -u origin HEAD`. Открыть PR в `main` с заголовком
`chore(release): X.Y.Z`. После зелёных `verify`, `security`, `commits`
`chore(release): X.Y.Z`. После зелёных обязательных проверок (`commits` в root;
`verify`, `security`, `commits` в остальных repo)
выполнить **fast-forward-only merge**. Если `main` изменился, сначала
согласовать историю и повторить проверку кандидата; не обходить защиту.
5. После merge получить `main` и убедиться, что его HEAD — проверенный релизный
@@ -185,25 +188,26 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
`release/hotfix-<name>` в `main`, затем обязательно возвращается в `develop`.
Если ломается публичный контракт, major сохраняется и для hotfix.
Backend CI проверяет runtime OpenAPI с frontend; docs CI — межрепозиторные
ссылки. Для `main`, PR в `main` и `release/*` соседи берутся из `.ci/refs.json`;
для разработки — из `develop`. SHA записываются в логи. Для изменения сразу
нескольких репозиториев сначала согласовать их revisions, затем проверять
релизный набор; порядок публикации сам по себе не доказывает совместимость.
Каждый CI использует только свой checkout. Backend генерирует OpenAPI без сервера,
сверяет генератор с configured app и не хранит JSON в Git; frontend проверяет types по входному
snapshot. Docs проверяет внутренние ссылки; внешние источники даются как
справочные веб-ссылки. Обновление API artifact потребителя — отдельное действие.
Зелёные локальные gates и порядок публикации не доказывают совместимость
развёрнутых версий. Процедуры выпуска находятся также в README каждого repo;
доступ к root для выпуска другого репозитория не требуется.
В Gitea для `main` и `develop` нужны запрет прямого push и force-push,
обязательные проверки `verify`, `security`, `commits` и запрет обхода правил
обязательные проверки: в root только `commits`, в остальных repo —
`verify`, `security`, `commits`; также нужен запрет обхода правил
администратором. Требование отдельного одобрения review не вводится для
разработки одним владельцем. Ветка по умолчанию — `develop`; Renovate тоже
отправляет PR в `develop`. Теги `v*` защищены, создание разрешается владельцу
релиза; их удаление или перенос не входит в обычный процесс.
`node test-release.cjs` из root проверяет настоящий release-it во временном Git.
Для проверки конфигурации соседа после установки его dependencies:
`node test-release.cjs frontend` (аналогично backend, docs, specs).
Тест не публикует refs и не использует рабочие базы. Root `npm run verify`
включает tracking и этот тест; frontend/backend/docs сохраняют свои gates,
backend дополнительно требует `npm run test:e2e`.
В root нет собственного test/verify runner и release hooks. Release-it и
commitlint используются напрямую. Остальные репозитории сохраняют свои gates.
Если в защите веток root ранее были обязательными `verify` и `security`, их
нужно убрать из required checks в Gitea; YAML не меняет настройки сервера.
Npm publishing, автоматический deploy и карточки Gitea Releases не включены.
Граница выпуска — принятый коммит, changelog и Git-тег.
-1
View File
@@ -6,7 +6,6 @@
"node": "24.21.0"
},
"scripts": {
"verify": "python3 test-workspace-tracking.py && node test-release.cjs && git diff --check",
"release:preview": "release-it --dry-run --ci",
"release:prepare": "release-it --ci",
"commitlint": "commitlint"
-10
View File
@@ -1,10 +0,0 @@
const assert = require("node:assert/strict");
const { execFileSync } = require("node:child_process");
const { version } = require("../package.json");
const git = (...args) => execFileSync("git", args, { encoding: "utf8" }).trim();
assert.equal(git("status", "--porcelain"), "", "Commit all changes before preparing a release");
const tags = git("tag", "--merged", "HEAD", "--list", "v[0-9]*", "--sort=-version:refname")
.split("\n").filter(tag => /^v\d+\.\d+\.\d+$/.test(tag));
assert.equal(version, tags[0]?.slice(1) ?? "0.0.0",
"Version is already prepared or release tags are missing. Fetch tags; finish the existing release before preparing another.");
-86
View File
@@ -1,86 +0,0 @@
// Exercise the actual release command in disposable repositories, never the workspace Git.
const assert = require('node:assert/strict');
const { execFileSync, spawnSync } = require('node:child_process');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const source = path.resolve(process.argv[2] || __dirname);
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'wealthledger-release-'));
const env = { ...process.env, HUSKY: '0', NO_COLOR: '1' };
const git = (...args) => execFileSync('git', args, { cwd, env, encoding: 'utf8' }).trim();
const read = file => fs.readFileSync(path.join(cwd, file), 'utf8');
const write = (file, text) => fs.writeFileSync(path.join(cwd, file), text);
const prepare = (...args) => spawnSync(process.execPath,
[path.join(source, 'node_modules/release-it/bin/release-it.js'), '--ci', ...args],
{ cwd, env, encoding: 'utf8' });
const mustPass = result => assert.equal(result.status, 0, result.stdout + result.stderr);
const mustFail = result => assert.notEqual(result.status, 0, result.stdout + result.stderr);
const commit = message => git('commit', '--allow-empty', '-qm', message);
try {
fs.mkdirSync(path.join(cwd, 'scripts'));
fs.symlinkSync(path.join(source, 'node_modules'), path.join(cwd, 'node_modules'), 'dir');
for (const file of ['.release-it.json', 'scripts/check-release.cjs', 'commitlint.config.cjs']) {
fs.copyFileSync(path.join(source, file), path.join(cwd, file));
}
write('.gitignore', 'node_modules/\nremote.git/\n');
write('CHANGELOG.md', '# Changelog\n');
write('package.json', JSON.stringify({ name: 'release-fixture', private: true, version: '0.0.0' }));
write('package-lock.json', JSON.stringify({ name: 'release-fixture', version: '0.0.0', lockfileVersion: 3, packages: { '': { name: 'release-fixture', version: '0.0.0' } } }));
git('init', '-q', '-b', 'main');
git('config', 'user.name', 'Release test');
git('config', 'user.email', 'release@example.invalid');
git('config', 'core.hooksPath', '/dev/null');
git('init', '--bare', '-q', 'remote.git');
git('remote', 'add', 'origin', path.join(cwd, 'remote.git'));
git('add', '.');
commit('chore: initialize fixture');
mustFail(prepare('0.1.0')); // main cannot prepare releases.
git('switch', '-qc', 'release/test');
write('untracked.txt', 'must block\n');
mustFail(prepare('0.1.0'));
fs.unlinkSync(path.join(cwd, 'untracked.txt'));
const before = ['package.json', 'package-lock.json', 'CHANGELOG.md'].map(read);
const head = git('rev-parse', 'HEAD');
mustPass(prepare('0.1.0', '--dry-run'));
assert.deepEqual(['package.json', 'package-lock.json', 'CHANGELOG.md'].map(read), before);
assert.equal(git('rev-parse', 'HEAD'), head);
mustPass(prepare('0.1.0'));
assert.equal(git('tag', '--list'), '', 'Preparation must not create tags');
assert.equal(git('log', '-1', '--format=%s'), 'chore(release): 0.1.0');
mustFail(prepare()); // Prepared version cannot be bumped again before tagging.
git('tag', '-a', 'v0.1.0', '-m', 'Release 0.1.0');
mustFail(prepare()); // No new commits.
for (const [message, expected] of [
['fix: correct calculation', '0.1.1'],
['feat: add export', '0.2.0'],
['docs: explain export', '0.2.1'],
['spec: define export', '0.2.2'],
['chore(deps): update dependencies', '0.2.3'],
['fix!: remove legacy contract', '1.0.0'],
['feat: change export\n\nBREAKING CHANGE: replace export format', '2.0.0'],
]) {
commit(message);
mustPass(prepare());
const pkg = JSON.parse(read('package.json'));
const lock = JSON.parse(read('package-lock.json'));
assert.equal(pkg.version, expected);
assert.equal(lock.version, expected);
assert.equal(lock.packages[''].version, expected);
assert.ok(read('CHANGELOG.md').includes(expected));
assert.equal(git('status', '--porcelain'), '');
git('tag', '-a', `v${expected}`, '-m', `Release ${expected}`);
}
assert.ok(read('CHANGELOG.md').includes('/commit/'), read('CHANGELOG.md'));
assert.ok(read('CHANGELOG.md').includes('/compare/v0.1.0...v0.1.1'));
assert.ok(!read('CHANGELOG.md').includes('chore(release)'));
assert.equal(read('CHANGELOG.md').split('correct calculation').length, 2, 'Prior changes must not be repeated');
assert.equal(spawnSync('git', ['--git-dir=remote.git', 'show-ref'], { cwd }).status, 1, 'Preparation must not push refs');
const lint = message => spawnSync(process.execPath,
[path.join(source, 'node_modules/@commitlint/cli/cli.js'), '--strict'],
{ cwd, env, input: message, encoding: 'utf8' });
mustPass(lint('spec: define contract'));
mustFail(lint('unstructured message'));
console.log(`PASS: release preparation, guards, version rules, lockfile and changelog (${source})`);
} finally {
fs.rmSync(cwd, { recursive: true, force: true });
}
-77
View File
@@ -1,77 +0,0 @@
"""Check root tracking on fictional data; never stage the real workspace."""
from pathlib import Path
import subprocess
import tempfile
def check(ignore_rules):
with tempfile.TemporaryDirectory(prefix="wealthledger-tracking-") as directory:
root = Path(directory)
def git(*args):
return subprocess.check_output(
["git", "-C", str(root), *args], text=True
)
git("init", "--quiet")
allowed = {
'.ai/conventions/git.md',
'.ai/conventions/dependencies.md',
'.ai/conventions/code-health.md',
'.ai/conventions/testing.md',
'.ai/conventions/guardrails.md',
'.ai/migrations/conventions-v1/audit.md',
'.ai/migrations/conventions-v1/plan.md',
'.gitea/workflows/commits.yml',
'.gitea/workflows/security.yml',
'.gitea/workflows/verify.yml',
'.gitignore',
'.nvmrc',
'.release-it.json',
'AGENTS.md',
'CHANGELOG.md',
'README.md',
'commitlint.config.cjs',
'package-lock.json',
'package.json',
'renovate.json',
'scripts/check-release.cjs',
'test-release.cjs',
'test-workspace-tracking.py',
}
for name in allowed:
(root / name).parent.mkdir(parents=True, exist_ok=True)
(root / name).write_text("fictional fixture\n")
(root / ".gitignore").write_text(ignore_rules)
for name in ("frontend", "backend", "docs", "specs"):
nested = root / name
nested.mkdir()
subprocess.run(["git", "init", "--quiet", str(nested)], check=True)
for name in (
"frontend/source.ts", "backend/prisma/dev.db", "docs/README.md",
"specs/README.md", "statement-imports/report.csv", "private.db",
"private.db-wal", ".env", ".env.example", "audit.pdf",
"design/preview.html", "todo/notes.md", "references/source.md",
".agents/local.md", "unknown-directory/data.txt", "unknown-file.txt",
"scripts/private.txt", ".gitea/private.txt", ".gitea/workflows/private.yml",
"node_modules/private.txt",
".ai/private.md", ".ai/conventions/private.md",
".ai/migrations/private.md", ".ai/migrations/conventions-v1/private.md",
".ai/migrations/other/private.md",
):
target = root / name
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text("fictional private data\n")
before = {p.relative_to(root): p.read_bytes() for p in root.rglob("*")
if p.is_file() and ".git" not in p.relative_to(root).parts[:1]}
git("add", ".")
assert set(git("ls-files").splitlines()) == allowed
assert all((root / p).read_bytes() == content for p, content in before.items())
assert all((root / name / ".git").is_dir()
for name in ("frontend", "backend", "docs", "specs"))
if __name__ == "__main__":
check(Path(__file__).with_name(".gitignore").read_text())
print("PASS: only root allowlist staged; fictional data and nested Git preserved")