The 9-task plan for the adr-kit / mermaid-skill / architecture-patterns
integration. Committed alongside the work it produced (commits b15a94a..93ac262).
cspell-words.txt: +inertiajs +Sev (plan-file vocabulary).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
adr-judge crashed (UnicodeEncodeError: surrogate '\udc98') when the staged diff contained non-ASCII content: Python reads piped stdin with the Windows cp1251 console codepage, not UTF-8, so a Cyrillic diff mis-decodes into surrogates and dies at diff_text.encode('utf-8'). '-X utf8' forces Python UTF-8 mode. Task 5's red-test probe was ASCII, so the crash went unseen until Task 6's Cyrillic docs/architecture files. adr-judge's file reads already use explicit encoding='utf-8'; only stdin was affected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
adr-judge v0.13.1 vendored from the adr-kit plugin (MIT) -> tools/adr-judge.py (819 lines, Python stdlib only). lefthook pre-commit job 9 runs 'git diff --cached --unified=0 | python tools/adr-judge.py --diff - --adr-dir docs/adr/'.
AK6 resolved: the --llm flag is NOT passed, so adr-judge runs declarative regex only — no Claude Sonnet call, zero economy cost. adr-kit's own git-hook template passes --llm; we deliberately do not, and lefthook keeps sole ownership of .git/hooks (AK1).
Verified: red test — staged @inertiajs/vue3 import in app/resources/js/ blocked with VIOLATION citing ADR-001 line 1, lefthook exit 1. Green test — clean diff, 9/9 jobs pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three seed ADRs to the adr-kit 7-section template: ADR-000 (process + docs/adr vs registry vs docs/architecture boundary), ADR-001 (Vue 3 + Vuetify 3 stack, with an Enforcement block forbidding Inertia/React/framer-motion/Tailwind imports), ADR-002 (PostgreSQL RLS multi-tenancy, documentation-only).
adr-lint: 3/3 PASS strictly (completeness + consistency). markdownlint 0 errors. .claude/adr-kit-guide.md vendored from the plugin (replaces what adr-kit:init would write to CLAUDE.md — AK2). cspell glossary += ADR/rvdbreemen/secondsky/NNN/MMM. init/install-hooks NOT run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
markdownlint-cli2 --fix: blanks-around-lists/fences в плане 5B.
0 errors. Pre-existing 26 ошибок в планах Sprint 4/5A — вне scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
План 6 задач портал-аудита Sprint 5B. T2 NAV_ITEMS поправлен 7→8
(добавлен «Импорт данных» /import — сверено с origin/main-сайдбаром).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-quality review T1: stale JSDoc «Counts — mock» теперь ложный
(count live из API); +поясняющий комментарий к null→undefined цепочке.
Comment-only, 0 изменений поведения. Vitest 6/6 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Слой WISHLIST: панель отложенных хотелок развития мозга/портала + кнопка-легенда «💡 Хотелки» в нижней легенде. Засеяно 4 хотелками раздела E8: K7-spike, мост claude-mem→ReasoningBank, claude-mem #1, двухуровневый ремонтник. Аддитивно — режим легенды наравне с «Разделы»; счётчики узлов/рёбер не меняются.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final review (🟢 low): SPA-маршрут /import работал через Route::fallback,
но все остальные app-маршруты перечислены явно в Route::view-блоке
(CLAUDE.md документирует явный список как намеренный паттерн — catch-all
перехватывал бы _test/* runtime-роуты Pest). /import добавлен в список
для консистентности и устойчивости.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final review нашёл: HistoricalImportService::loadStatusOverrides и
persistUnknownStatuses запрашивали import_unknown_statuses без явного
where(tenant_id), полагаясь на RLS через SET LOCAL. Но queue worker на prod
работает под crm_supplier_worker — BYPASSRLS-роль (00_create_roles.sql §5),
SET LOCAL не фильтрует → cross-tenant утечка: импорт тенанта A мог подхватить
resolved-маппинг тенанта B и инкрементировать его occurrences.
Добавлен явный where(tenant_id) в обе выборки (конвенция defense-in-depth
00_create_roles.sql:64 — WHERE-фильтры обязательны под BYPASSRLS). +тест
cross-tenant изоляции (red-green verified: без фикса 'Архив' тенанта A
получал status 'closed' из чужого маппинга).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review Task 10 (🟡): магическое число 2000 (интервал polling'а) вынесено
в именованную константу POLL_INTERVAL_MS — паттерн файла (как в DashboardView).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TDD: spec (3 tests) first, then component.
ImportView.vue: upload form + polling + history table + unknown-statuses banner.
Uses api/imports (uploadImport/listImports/getImport/getUnknownStatuses).
setInterval callback wrapped in named async fn (pollOnce) — no eslint-disable needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review Task 9 (🟡): добавлен тест защиты show() — пользователь одного
тенанта получает 403 при запросе import_log другого тенанта (покрывает
abort_if defense-in-depth в ImportController::show). phpstan-baseline
регенерирован — инкремент count ложного TestCall-срабатывания (квирк 25).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ShouldQueue-job: читает CSV через Storage::disk('local'), парсит через
CsvLeadsParser, импортирует через HistoricalImportService (4 аргумента),
обновляет import_log (pending→processing→done|failed), шлёт
ImportCompletedNotification. RLS через SET LOCAL в каждой транзакции.
tries=1 (идемпотентность на уровне строк, повторный прогон искажает
счётчики — авто-ретрай отключён). Larastan: 0 новых ошибок.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review Task 6 (non-blocking 🟡): HistoricalImportService объявлен final
(симметрия с ImportResult, утилитарный сервис без наследования). Ключ ошибки
upsert'а переименован 'line' → 'source_crm_id' — поле хранит идентификатор из
исходной CRM, а не файловую строку (в отличие от CsvParseResult::errors).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Реализован HistoricalImportService с ImportResult DTO и 7 feature-тестами
(TDD). Идемпотентный upsert через pg_advisory_xact_lock + webhook_dedup_keys;
создание партиций через MonthlyPartitionManager; напоминания; unknown-статусы
с tenant-переопределениями; dry_run режим; historical_import tx без списания
баланса. Попутный fix CarbonImmutable-петли в MonthlyPartitionManager::ensureRange.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review Task 5 (non-blocking 🟡): CsvLeadsParser объявлен final (симметрия
с DTO ParsedLeadRow/CsvParseResult, утилитарный класс без наследования);
строка ошибки про число колонок использует self::EXPECTED_COLUMNS вместо
литерала 9.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Парсер CSV-выгрузки лидов crm.bp-gr.ru (ТЗ §6.2/§6.3): срезает UTF-8 BOM,
разбирает строки через str_getcsv, валидирует телефон (7XXXXXXXXXX) и даты
(Y/m/d H:i:s), срезает префикс B[123]_ из названия проекта. Невалидные
строки не роняют парсинг — собираются в errors[] с абсолютным номером строки.
Тесты: 5/5 (unit, без DB).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Выносит DDL-логику создания месячных RANGE-партиций из команды
PartitionsCreateMonths в переиспользуемый сервис MonthlyPartitionManager.
Сервис используется командой (DRY) и будет использован HistoricalImportService
для партиций под исторические даты CSV.
- MonthlyPartitionManager::ensureRange(table, from, to) — гарантирует партиции
под диапазон дат, идемпотентно; отвергает незарегистрированные таблицы
- MonthlyPartitionManager::ensureMonth(table, monthStart) — одна партиция
- PartitionsCreateMonths рефакторена: убраны PARTITIONED_TABLES, partitionExists(),
use DB; inject MonthlyPartitionManager через handle()
- Test: MonthlyPartitionManagerTest (3 теста, DatabaseTransactions — DDL откат)
- Regression: PartitionsCreateMonthsTest (4 теста) — зелёный, поведение не изменилось
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>