diff --git a/cspell-words.txt b/cspell-words.txt index 360c14c2..3aa67dff 100644 --- a/cspell-words.txt +++ b/cspell-words.txt @@ -853,6 +853,7 @@ SRP коммитится OKLCH ребрендинга +gstatic # v1.81 — Plugin Stack Rules v1.3 (Frontend Design + Superpowers paired stack) инвокация diff --git a/docs/superpowers/specs/2026-05-09-sprint1-hygiene-design.md b/docs/superpowers/specs/2026-05-09-sprint1-hygiene-design.md new file mode 100644 index 00000000..47fe080f --- /dev/null +++ b/docs/superpowers/specs/2026-05-09-sprint1-hygiene-design.md @@ -0,0 +1,347 @@ +# Spec: Спринт 1 «Hygiene» — исправление дефектов аудита 2026-05-09 + +**Версия:** 1.0 +**Дата:** 09.05.2026 +**Автор:** Claude Code (skill: superpowers:brainstorming) +**Заказчик:** Дмитрий +**Статус:** черновик, ждёт review заказчика + +## 1. Цель + +Закрыть быстрые/безопасные находки аудита [docs/audit_2026-05-09.md](../../audit_2026-05-09.md) (commit `b6ae8dd`): + +- **Все 3 P0** (security/RLS + сломанный конфиг) — обязательно. +- **10 из 12 P1** (P1-10/P1-11 → реестр, не правки). +- **Все 3 P2** (мелочи). +- **6 low-risk O-\*** (effort=S, risk=low): 2 missing FK indices, password rules trait, ESLint anti-vuetify-import, npm outdated CI workflow, font-display docs. + +**Не входит в Sprint 1:** все большие рефакторинги (DealController/AuthController/12 Vue-компонентов >300 строк), все O-perf кроме индексов, любые миграции стека (Pest 4 browser-tests, Vue 3.5 фичи). Это Спринт 2/3 — отдельный flow. + +**Wall-clock:** 3-5 часов агентов в субагентном режиме. + +## 2. Scope — 22 правки + 3 записи в реестр + +### Дефекты (16) + +- **3 P0:** P0-01, P0-02, P0-03 +- **10 P1:** P1-01, P1-02, P1-03, P1-04, P1-05, P1-06, P1-07, P1-08, P1-09, P1-12 +- **3 P2:** P2-01, P2-02, P2-03 + +### Out-of-scope (2 P1 → реестр без правок) + +- **P1-10** (auth на /api/deals) — добавить в `docs/Открытые_вопросы_v8_3.md` как новый CTO-вопрос pre-prod migration. +- **P1-11** (auth на /api/admin) — уже = Б-1; добавить cross-link. + +### Возможности low-risk (6 из 24 O-\*) + +- **O-perf-02:** index `failed_webhook_jobs.webhook_log_id` +- **O-perf-03:** index `rejected_deals_log.webhook_log_id` +- **O-refactor-03:** `HasPasswordRules` trait для FormRequest +- **O-refactor-05:** ESLint `no-restricted-imports` rule против `vuetify/components` +- **O-stack-08:** GitHub Actions workflow с `npm outdated` (еженедельно) +- **O-stack-09:** документация `font-display: swap` + WOFF2 в handoff + +## 3. Архитектура — 6 фаз / 6 коммитов + +``` +[A. DB] → schema.sql v8.10 → v8.11 (RLS + 2 FK indices) + CHANGELOG_schema + ↓ +[B. Backend] → app/ register middleware + trait + test fix + ↓ +[C. Configs] → 6 config files (npm/lychee/pa11y/composer/eslint/ci) + ↓ +[D. Docs (narrative)] → README + CLAUDE.md + Pravila + Tooling + cspell-words + ↓ +[E. Docs (handoff)] → BRANDBOOK + DEVELOPER_HANDOFF (status mapping + axe doc + font-display) + ↓ +[F. Registry] → Открытые_вопросы (CTO + Histoire-Vite trigger) +``` + +**Зависимости:** + +- B зависит от A (B упоминает CHANGELOG schema v8.11, который пишется в A). +- D зависит от A (D обновляет метрики schema в README/CLAUDE.md). +- E/F не зависят от A-D — могут идти параллельно после A-D, но для простоты выполняются последовательно. + +Каждая фаза — отдельный git commit со своим осмысленным scope. После каждой фазы (где применимо) — verification (см. §6). + +## 4. Детальный scope по фазам + +### Фаза A — DB + +**Файлы:** `db/schema.sql`, `db/CHANGELOG_schema.md`. + +**Изменения:** + +1. **P0-02:** добавить после `CREATE TABLE impersonation_tokens` (строка 510): + + ```sql + ALTER TABLE impersonation_tokens ENABLE ROW LEVEL SECURITY; + CREATE POLICY tenant_isolation ON impersonation_tokens + USING (tenant_id = current_setting('app.current_tenant_id')::bigint); + ``` + +2. **O-perf-02:** добавить после индексов `failed_webhook_jobs`: + + ```sql + CREATE INDEX idx_failed_webhook_jobs_log ON failed_webhook_jobs(webhook_log_id); + ``` + +3. **O-perf-03:** добавить после индексов `rejected_deals_log`: + + ```sql + CREATE INDEX idx_rejected_deals_log_webhook ON rejected_deals_log(webhook_log_id); + ``` + +4. **CHANGELOG_schema.md:** добавить запись v8.10 → v8.11 со ссылкой на audit P0-02 + O-perf-02/03. + +**Шапка schema.sql:** обновить версию v8.10 → v8.11, метрики 37 RLS → 38 RLS, 95 индексов → 97 индексов. + +**Verification:** squawk на schema.sql; повторный grep на ENABLE RLS = 40, POLICY = 38; визуальная проверка diff. + +### Фаза B — Backend + +**Файлы:** `app/app/Http/Kernel.php`, `app/routes/web.php`, `app/app/Http/Requests/Auth/Concerns/HasPasswordRules.php` (создать), `app/app/Http/Requests/Auth/LoginRequest.php`, `app/app/Http/Requests/Auth/RegisterRequest.php`, `app/tests/Feature/AdminIncidentsIndexTest.php`. + +**Изменения:** + +1. **P0-01:** в `app/Http/Kernel.php` зарегистрировать alias: + + ```php + protected $middlewareAliases = [ + // ... existing + 'tenant' => \App\Http\Middleware\SetTenantContext::class, + ]; + ``` + + В `routes/web.php` обернуть tenant-маршруты (Deal/Notification/Reminder/Report/Webhook) в группу `Route::middleware(['tenant'])->group(...)`. Сохранить тестовый flow с `tenant_id` query-param (через middleware читать оба источника: header + query). + + **Risk note:** middleware дополняет, не заменяет существующий MVP-flow. На prod-миграции (Б-1) `tenant_id`-param будет удалён, останется только middleware. + +2. **O-refactor-03:** создать trait `HasPasswordRules`: + + ```php + namespace App\Http\Requests\Auth\Concerns; + + trait HasPasswordRules + { + protected function passwordRules(): array + { + return ['required', 'string', 'min:8']; + } + } + ``` + + Подключить в `LoginRequest::rules()` и `RegisterRequest::rules()`. + +3. **P2-01:** в `tests/Feature/AdminIncidentsIndexTest.php` заменить `bcrypt('test')` на `bcrypt('test1234')` (≥8 chars). + +**Verification:** `cd app && composer test` — Pest 416/416 PASS. `composer stan` — 0 errors. + +### Фаза C — Configs + +**Файлы:** `package.json`, `.lychee.toml`, `pa11y.config.json`, `app/composer.json`, `app/eslint.config.js` (или новый `.eslintrc-vuetify-rules.js`), `.github/workflows/dependency-check.yml` (создать). + +**Изменения:** + +1. **P0-03:** в `package.json:13` заменить `/tmp/schema-formatted.sql` на `db/.schema-formatted.tmp.sql`. Добавить `db/.schema-formatted.tmp.sql` в `.gitignore`. + +2. **P1-02:** в `.lychee.toml` добавить exclude для `web/v8/*.html` (root-relative ссылки концептов): + + ```toml + exclude = [ + # ... existing + "^/(login|register|legal|dashboard|deals|admin|reports|reminders|billing|impersonation|notifications)", + ] + ``` + +3. **P1-12:** в `pa11y.config.json` обновить пути с `web/01-login.html` (несуществующих) на `liderra_v8_handoff/concepts/v8_*.html` (фактические). + +4. **P1-07:** в `app/composer.json` `scripts` добавить: + + ```json + "audit-offline": "@composer audit --locked --no-network" + ``` + +5. **O-refactor-05:** в `app/eslint.config.js` добавить правило `no-restricted-imports`: + + ```js + { + 'no-restricted-imports': ['error', { + paths: [{ + name: 'vuetify/components', + message: 'Use auto-import via vite-plugin-vuetify (see vite.config.ts)', + }], + }], + } + ``` + +6. **O-stack-08:** создать `.github/workflows/dependency-check.yml`: + + ```yaml + name: Dependency Check + on: + schedule: [{ cron: '0 9 * * 1' }] # каждый понедельник 09:00 UTC + workflow_dispatch: + jobs: + outdated: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: { node-version: '20' } + - run: npm install --ignore-scripts + - run: npm outdated --json > outdated.json || true + - name: Open issue if outdated + if: ${{ hashFiles('outdated.json') != '' }} + run: gh issue create --title "Weekly outdated check $(date)" --body "$(cat outdated.json)" --label dependencies + env: { GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} } + ``` + +**Verification:** `npm run format:sql:check` (на Windows) — больше не «system cannot find path»; `npm run links` — 0 errors на `docs/**` (web/v8/* исключён); ESLint smoke на правках Vue-файлов; pre-commit hooks PASS. + +### Фаза D — Docs (narrative) + +**Файлы:** `README.md`, `CLAUDE.md`, `docs/Pravila_raboty_Claude_v1_1.md`, `docs/Tooling_v8_3.md`, `cspell-words.txt`. + +**Изменения:** + +1. **P1-01:** в `README.md:83-90` синхронизировать с CLAUDE.md: + - Tooling v1.0 → v1.10 + - Pravila v1.2 → v1.6 + - schema v8.5 → v8.11 (после Фазы A) + - 54 таблицы / 91 индекс / 34 RLS → 56 таблиц / 97 индексов / 38 RLS (после Фазы A) + +2. **P1-03:** в `CLAUDE.md:192` (и §3.3) обновить «Histoire 21/28» → «Histoire 21/43». + +3. **P1-06:** в `docs/Pravila_raboty_Claude_v1_1.md:613` (приблизительно) добавить версию в ссылку: `[Plugin_stack_rules_v1.md](Plugin_stack_rules_v1.md) (v1.3)`. + +4. **P1-08:** в `docs/Tooling_v8_3.md` Прил. Н §4.2 п.22 дополнить: `stylelint-config-standard ^40.0.0`. + +5. **P2-02:** добавить `ребрендинга` в `cspell-words.txt`. (**Уже добавлено** во время self-review аудита 09.05.2026 — verify, не дублировать.) + +6. **P2-03:** в шапку `CLAUDE.md` рядом с упоминанием «6 трений F–K» добавить ссылку: «(детали в [Plugin_stack_rules_v1.md История версий](docs/Plugin_stack_rules_v1.md))». + +**CLAUDE.md правки — через `claude-md-management:claude-md-improver`** (CLAUDE.md §5 п.10 — единственный путь). Pravila/Tooling — то же самое (плагин охватывает их по §0 priority chain). + +**Verification:** `npm run check:docs` (markdownlint + cspell + lychee + a11y); pre-commit hooks PASS. + +### Фаза E — Docs (handoff) + +**Файлы:** `liderra_v8_handoff/docs/BRANDBOOK_v2.md`, `liderra_v8_handoff/docs/DEVELOPER_HANDOFF.md`. + +**Изменения:** + +1. **P1-04:** в `DEVELOPER_HANDOFF.md:430+518` явно указать дату прогона axe-core (если есть в архиве) или пометить как «требует подтверждения после фикса pa11y.config (см. P1-12)». Не удалять заявление, но связать с воспроизводимым evidence. + +2. **P1-05:** в `BRANDBOOK_v2.md` после §3.6 (14 OKLCH-статусов) добавить таблицу: + + | Slug (schema) | Имя BRANDBOOK | OKLCH | Статус-код | + |---|---|---|---| + | new | Новая | ... | ... | + | viewed | Просмотрена | ... | ... | + | ... (14 строк всего) | | | | + + Извлечь slug'и из `db/schema.sql:2172-2186` (INSERT lead_statuses), сопоставить с 14 цветами BRANDBOOK по hue-порядку. Если сопоставление неоднозначно — оставить TODO с явным вопросом дизайнеру. + +3. **O-stack-09:** в `DEVELOPER_HANDOFF.md:250-258` (§4 Типографика) добавить раздел «Font loading strategy»: + - Объяснить `&display=swap` (FOUT, fallback сразу). + - WOFF2 формат по умолчанию из Google Fonts (лучше сжатие). + - `` на `fonts.gstatic.com` для ускорения. + +**Verification:** `npm run check:docs`; визуальная проверка таблицы. + +**Note:** handoff правки — в репозитории `liderra_v8_handoff/`. Если этот dir не входит в наш git (sub-module / external) — Фаза E пропускается, фиксируется как «требует push в handoff-репо отдельно». + +### Фаза F — Registry + +**Файлы:** `docs/Открытые_вопросы_v8_3.md`. + +**Изменения:** + +1. **P1-10 → новый CTO-вопрос:** + + ```markdown + ### CTO-XX (новый, открыт 09.05.2026) + **Заголовок:** auth+tenant middleware на /api/deals на pre-prod миграции + **Контекст:** MVP использует tenant_id query-param. На prod-миграции + обязательно перейти на header `X-Tenant-Id` + middleware('tenant'). + **Trigger закрытия:** prod-миграция (после Б-1). + **Связанные находки:** audit_2026-05-09.md P1-10. + ``` + +2. **P1-11 → cross-link к Б-1:** + + ```markdown + В разделе Б-1 (admin SSO) добавить упоминание /api/admin/* без auth = тот же блокер. + ``` + +3. **P1-09 → новый OPEN-вопрос:** + + ```markdown + ### OPEN-XX — Histoire ↔ Vite 8 миграционный долг + **Контекст:** Histoire 1.0-beta.1 установлен через --legacy-peer-deps. + **Trigger закрытия:** релиз Histoire с peerDep `vite ^8`. + ``` + +**Verification:** `npm run links` — 0 errors на новые ссылки. + +## 5. Definition of Done + +Sprint 1 завершён, когда: + +1. ✅ Все 22 правки из §2 + §4 применены, по одной фазе на коммит. +2. ✅ `cd app && composer test` — Pest 416/416 PASS (после Фазы B). +3. ✅ `cd app && composer stan` — 0 errors above baseline. +4. ✅ `cd app && npm run type-check` — 0 type errors. +5. ✅ `cd app && npm run test:vue` — Vitest 393/393 PASS. +6. ✅ `npm run check:docs` (markdownlint + cspell + lychee + a11y) — 0 errors. +7. ✅ Pre-commit hooks lefthook PASS на каждом коммите. +8. ✅ schema метрики обновлены до v8.11 (38 RLS / 97 индексов). +9. ✅ Git log: 6 коммитов с осмысленными scope-сообщениями. +10. ✅ После Фазы F: 3 новые/обновлённые записи в `docs/Открытые_вопросы_v8_3.md`. + +**Не входит в DoD:** + +- Реализация O-* за пределами 6 заявленных. +- Прогон Pa11y на handoff концептах (требует браузер-сессии — следствие P1-12 fix, может быть pre-prod manual step). +- Спринт 2/3 (modernization, big refactors). + +## 6. Бюджет + +| Фаза | Wall-clock | +|---|---| +| A. DB | 15-20 мин | +| B. Backend | 30-40 мин | +| C. Configs | 30-40 мин | +| D. Docs (narrative) | 30-45 мин (через claude-md-management) | +| E. Docs (handoff) | 30-45 мин | +| F. Registry | 10-15 мин | +| **Итого** | **2.5-3.5 часа** | + +## 7. Риски + +- **Риск:** RLS-policy на impersonation_tokens может сломать существующие тесты, если они не используют `SET LOCAL app.current_tenant_id`. Митигация: после Фазы A прогнать Pest, если что-то падает — добавить `SET LOCAL` в test setup. +- **Риск:** SetTenantContext middleware регистрация может перехватить тестовые маршруты с tenant_id query-param. Митигация: в Фазе B middleware читает оба источника (header + query) до prod-миграции. +- **Риск:** Фаза E (handoff) может попасть в внешний репозиторий — если так, фаза формально пропускается с пометкой «требует ручной push». +- **Риск:** Фаза D правки CLAUDE.md/Pravila обязательно через `claude-md-management:claude-md-improver` (CLAUDE.md §5 п.10). Прямые Edit/Write этих файлов без skill = нарушение. + +## 8. Плагины и MCP + +| Плагин / Skill | Применение | +|---|---| +| `superpowers:writing-plans` | Сразу после approve этого spec'а (для implementation plan) | +| `superpowers:subagent-driven-development` | Исполнение plan'а в этой же сессии | +| `superpowers:verification-before-completion` | После каждой фазы перед коммитом | +| `claude-md-management:claude-md-improver` | **Обязательно** для Фазы D правок CLAUDE.md/Pravila/Tooling (CLAUDE.md §5 п.10) | +| `laravel-boost` MCP | По необходимости в Фазе B (Eloquent, маршруты) | +| `github` MCP | По необходимости в Фазе C для GitHub Actions workflow | +| Frontend Design plugin | НЕ призывается (R10: аудит-фиксы ≠ дизайн) | +| simplify / security-review / review / init / ui-ux-pro-max | НЕ призываются (R10: только по явному /команде) | + +## 9. Не входит в этот spec + +- Спринт 2 «Modernization» (Pest 4 browser/mutation, Vue 3.5 фичи, Vuetify 3.12 типизированные слоты, Laravel 13 lazy-loading) — отдельный spec→plan→execute. +- Спринт 3 «Big refactors» (DealController split, AuthController split, 12 Vue-компонентов >300 строк, OFFSET → keyset, export streaming, CLAUDE.md §0 reorg) — отдельный spec→plan→execute. +- Pa11y prod-режим на handoff — manual user step после Фазы C (P1-12 fix). +- Регистрация Frontend Design plugin в `~/.claude/settings.json` (O-stack-06) — manual user step, файл вне git.