From 95e4361599244598126394aa658a0a27d77c0686 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Tue, 15 Sep 2026 20:45:36 +0300 Subject: [PATCH] chore: simplify root tooling and repository boundaries --- .ai/conventions/code-health.md | 17 +++---- .ai/conventions/dependencies.md | 8 +-- .ai/conventions/git.md | 8 ++- .ai/conventions/guardrails.md | 57 +++++----------------- .gitea/workflows/commits.yml | 4 +- .gitea/workflows/security.yml | 31 ------------ .gitea/workflows/verify.yml | 17 ------- .gitignore | 7 --- .release-it.json | 3 -- AGENTS.md | 45 ++++++++--------- README.md | 76 +++++++++++++++-------------- package.json | 1 - scripts/check-release.cjs | 10 ---- test-release.cjs | 86 --------------------------------- test-workspace-tracking.py | 77 ----------------------------- 15 files changed, 89 insertions(+), 358 deletions(-) delete mode 100644 .gitea/workflows/security.yml delete mode 100644 .gitea/workflows/verify.yml delete mode 100644 scripts/check-release.cjs delete mode 100644 test-release.cjs delete mode 100644 test-workspace-tracking.py diff --git a/.ai/conventions/code-health.md b/.ai/conventions/code-health.md index a20abcf..6662e11 100644 --- a/.ai/conventions/code-health.md +++ b/.ai/conventions/code-health.md @@ -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. diff --git a/.ai/conventions/dependencies.md b/.ai/conventions/dependencies.md index 861c5fd..8f4c908 100644 --- a/.ai/conventions/dependencies.md +++ b/.ai/conventions/dependencies.md @@ -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). diff --git a/.ai/conventions/git.md b/.ai/conventions/git.md index 9293a24..9d1aab0 100644 --- a/.ai/conventions/git.md +++ b/.ai/conventions/git.md @@ -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` запрещён; версии вручную не повышай. diff --git a/.ai/conventions/guardrails.md b/.ai/conventions/guardrails.md index a4fd3ca..44f1fe8 100644 --- a/.ai/conventions/guardrails.md +++ b/.ai/conventions/guardrails.md @@ -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#релизы-и-ветки). diff --git a/.gitea/workflows/commits.yml b/.gitea/workflows/commits.yml index 2372d2c..c4c8d9e 100644 --- a/.gitea/workflows/commits.yml +++ b/.gitea/workflows/commits.yml @@ -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" diff --git a/.gitea/workflows/security.yml b/.gitea/workflows/security.yml deleted file mode 100644 index 247a371..0000000 --- a/.gitea/workflows/security.yml +++ /dev/null @@ -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 diff --git a/.gitea/workflows/verify.yml b/.gitea/workflows/verify.yml deleted file mode 100644 index b64c773..0000000 --- a/.gitea/workflows/verify.yml +++ /dev/null @@ -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 diff --git a/.gitignore b/.gitignore index de0d4e3..b09f6dd 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.release-it.json b/.release-it.json index e5f07b0..f2bbfa2 100644 --- a/.release-it.json +++ b/.release-it.json @@ -18,9 +18,6 @@ "gitlab": { "release": false }, - "hooks": { - "after:git:init": "node scripts/check-release.cjs" - }, "plugins": { "@release-it/conventional-changelog": { "infile": "CHANGELOG.md", diff --git a/AGENTS.md b/AGENTS.md index 5b51b01..e48df9a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/README.md b/README.md index bea7cda..afb1ce0 100644 --- a/README.md +++ b/README.md @@ -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 --to ` | Формат сообщений коммитов | | 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/` от проверенного `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-` в `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-тег. diff --git a/package.json b/package.json index 8378098..5234711 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/scripts/check-release.cjs b/scripts/check-release.cjs deleted file mode 100644 index a2c957b..0000000 --- a/scripts/check-release.cjs +++ /dev/null @@ -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."); diff --git a/test-release.cjs b/test-release.cjs deleted file mode 100644 index bc8ae03..0000000 --- a/test-release.cjs +++ /dev/null @@ -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 }); -} diff --git a/test-workspace-tracking.py b/test-workspace-tracking.py deleted file mode 100644 index a15a797..0000000 --- a/test-workspace-tracking.py +++ /dev/null @@ -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") -- 2.54.0