Compare commits

..

575 Commits

Author SHA1 Message Date
Дмитрий 05706ef429 @
docs(a3): cspell-words.txt +ребейз-family

ребейз/ребейзнута/ребейзом — слова из CLAUDE.md §6/§9 и spec §7
(описание ребейза feat/a3 на origin/main). Единственные реально новые
термины A3-нормативки по cspell-прогону добавленных строк.

Task 10 плана A3 integration-tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 16:06:34 +03:00
Дмитрий 35b48c1b0c @
docs(a3): CLAUDE.md v2.9 — register #47 openapi-mcp-server (A3 integration-tooling)

§3 title 46→47; §3.3 +строка #47 openapi-mcp-server; §1 row 2b 46→47;
§3.3 footer 46→47 + integration-tooling 9-я off-phase подкатегория
(17 off-phase); §0 cross-refs Pravila v1.23 / PSR_v1 v3.9 / Tooling v2.9;
§6 +абзац A3; §9 +запись. Шапка v2.8→v2.9.

Через /claude-md-management:claude-md-improver (§5 п.10).
Task 9 плана A3 integration-tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 16:00:52 +03:00
Дмитрий 046c8b6efa @
docs(a3): Pravila v1.23 — §13.2 +Off-phase integration-tooling

§13.2 +абзац «Off-phase integration-tooling»: #47 openapi-mcp-server
(Tooling §4.22) + api-docs agent (узел карты A3 без Tooling-номера).
Не UI → вне R6/R14. Регулируются PSR_v1 R10.1 Блок 3. v1.22→v1.23.

Task 8 плана A3 integration-tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 15:54:52 +03:00
Дмитрий fc5f58a992 @
docs(a3): PSR_v1 v3.9 — R10.1 Блок 3 +openapi-mcp (integration-tooling)

R10.1 Блок 3 (MCP-серверы) +1 строка openapi-mcp-server — категория
integration-tooling, off-phase, раздел A3. Не UI → вне R6/R14.
Tooling §4.22 #47. Версия v3.8→v3.9.

Task 7 плана A3 integration-tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 15:50:02 +03:00
Дмитрий b51d5fb31d @
docs(a3): Tooling Прил. Н v2.9 — register #47 openapi-mcp-server (§4.22)

§4.22 — openapi-mcp-server (@ivotoby/openapi-mcp-server v1.14.0, MIT),
9-я off-phase подкатегория integration-tooling. §0 счётчик 46→47
(17 off-phase, 67 total). Парный узел карты — api-docs agent (без
Tooling-номера). Статус: verified.

Task 6 плана A3 integration-tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 15:47:07 +03:00
Дмитрий 10b19df1c4 @
feat(map): A3 nodes — api-docs agent + openapi MCP

2 новых узла раздела A3 «Программирование — интеграции»: ag_apidocs
(api-docs agent, claude-flow) + mcp_openapi (openapi MCP, #47). NODES /
NODE_SECTION / NODE_DETAILS nd() / NODE_TIMELINE / EDGES (3 ребра).
pos()-углы 4/175 + 5/5 после Grep-проверки коллизий. Счётчик 116→118.

Task 4 плана A3 integration-tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 15:39:30 +03:00
Дмитрий df4532d2fd @
feat(map): NODE_SECTION_SECONDARY layer — cross-ref nodes into A3

Аддитивный слой NODE_SECTION_SECONDARY (NODE_SECTION 1:1 не трогается):
кросс-реф mcp_boost/context7/ag_pest/mcp_semgrep/mcp_sentry в раздел A3.
SECTION_NODES build + Паспорт «Раздел» (формат «A1 (+A3)») обновлены.

Task 3 плана A3 integration-tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 15:34:41 +03:00
Дмитрий d85b9391cc @
docs(a3): re-baseline spec+plan onto origin/main 1313d89

feat/a3 ребейзнута на актуальный origin/main (был форк от D3-эры).
C9/deptrac/A4 уже влиты → openapi-mcp #41→#47, Tooling §4.16→§4.22,
integration-tooling 7-я→9-я off-phase подкатегория. Версии:
Tooling v2.8→v2.9, PSR_v1 v3.8→v3.9, Pravila v1.22→v1.23, CLAUDE.md
v2.8→v2.9. Карта 116→118 узлов. Stale line-anchors → Grep-by-symbol.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 15:32:03 +03:00
Дмитрий 2018959fdc @
feat(a3): register openapi-mcp-server in .mcp.json

openapi MCP server (@ivotoby/openapi-mcp-server v1.14.0, MIT, stdio) —
отдаёт docs/api/openapi.yaml как MCP-ресурс/тулы. Smoke verified
(npx --help, native-Windows OK). Конфиг — env-vars API_BASE_URL +
OPENAPI_SPEC_PATH (README stdio-форма).

Task 2 плана A3 integration-tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 15:20:43 +03:00
Дмитрий ff3979d527 @
docs(a3): OpenAPI skeleton for /api/deals — A3 smoke artifact

Стартовый OpenAPI 3.1 скелет для группы /api/deals* (8 эндпоинтов)
как smoke-доказательство api-docs-тулинга. Redocly lint — valid (exit 0,
2 warning о неполноте, ожидаемо для скелета). Не полная спека API.

Task 1 плана A3 integration-tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 15:19:19 +03:00
Дмитрий 756a8838d6 @
docs(a3): A3 integration-tooling implementation plan

10 задач: api-docs smoke → openapi-mcp install → карта (NODE_SECTION_SECONDARY
слой + 2 узла) → нормативка (Tooling/PSR_v1/Pravila/CLAUDE.md) → регрессия+память.

Точные якоря карты: NODE_SECTION (110 узлов), SECTION_NODES build (1973-1977),
ld-section (2082-2083), форматы ag_pest/mcp_boost. Риск кросс-веточной
нумерации с A11/C9 — Task 10 Step 2.

cspell-words.txt +redocly/ivotoby. Через superpowers:writing-plans.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 15:19:19 +03:00
Дмитрий a319e4f98a @
docs(a3): A3 integration-tooling design spec

Дизайн интеграции раздела A3 «Программирование — интеграции (API, вебхуки)»
карты automation-graph.html — параллельно A6/D3.

- 2 новых узла: api-docs agent (claude-flow, 0-install) + openapi-mcp-server
  (npm/stdio MCP, Tooling #41 §4.16)
- 5 кросс-реф узлов через новый аддитивный слой NODE_SECTION_SECONDARY
  (context7/Boost/Pest/Semgrep/Sentry — NODE_SECTION 1:1 не ломается)
- Нормативка: Tooling v2.5, PSR_v1 v3.5, Pravila v1.19, CLAUDE.md v2.5
- Риск кросс-веточной нумерации с A11/C9 зафиксирован

cspell-words.txt +4 валидных термина (аудировал/JVM/хендлеров/ivo).
Через superpowers:brainstorming (2 развилки сняты с заказчиком).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 15:19:19 +03:00
Дмитрий 1313d89525 docs(a4): add A4 design-tooling integration plan
The 8-task plan executed for the A4 epic, with the post-flight Plan Correction block (FM2 defer, #44-46 numbering, ADR-006, knowledge-work-plugins marketplace, /plugin unavailable in VSCode-extension env).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 14:07:26 +03:00
Дмитрий bcce4d9986 feat(a4): register Universal Icons MCP #45 in .mcp.json
Off-phase A4 design-tooling. Smoke verified post-reload: health_check OK, get_icon home/lucide returns a valid framework-neutral SVG. Comment ADR ref corrected ADR-004 -> ADR-006.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 14:07:25 +03:00
Дмитрий a718bb951f fix(a4): correct #46 Design plugin marketplace -> knowledge-work-plugins
The 'design' plugin lives in anthropics/knowledge-work-plugins (same marketplace as #42 product-management), not claude-plugins-official (which carries only frontend-design). Verified post-reload against the marketplace manifest. Pre-push fixup of 621498a's own error - v2.8/v3.8/v2.8 unchanged. Tooling 4.21 also completes the capability list (+Design System Management, +Dev Handoff).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 14:07:15 +03:00
Дмитрий 621498acc9 docs(a4): register #44-46 design-tooling — Tooling v2.8 / PSR_v1 v3.8 / Pravila v1.22 / CLAUDE.md v2.8 2026-05-17 13:03:58 +03:00
Дмитрий cafa8dfe2d fix(map): mcp_figma/mcp_icons → R10.1 блок 3 (MCP-серверы, не блок 1) 2026-05-17 12:40:49 +03:00
Дмитрий 8d9183c3ac feat(map): add mcp_figma/mcp_icons/design_plugin nodes — closes section A4 (3→6) 2026-05-17 12:35:00 +03:00
Дмитрий 0cea2cc320 docs(adr): ADR-006 — A4 design-tooling boundaries (FM1/DP1/DP2) 2026-05-17 12:29:09 +03:00
Дмитрий 9b63e27825 feat(map): deptrac node — extends section A6 to 4 nodes
automation-graph.html — new `deptrac` node (architecture-tooling),
NODE_SECTION → A6 (раздел «Архитектура систем» 3→4 узла), edge
psr_v1→deptrac, NODE_DETAILS + NODE_META entries. Smoke-tested:
113 nodes / 118 edges, 0 JS errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:32:37 +03:00
Дмитрий 0c98524357 docs(deptrac): register #43 deptrac architecture-tooling in 4 normative files
Tooling Прил. Н v2.6→v2.7 (§4.18 new, §0 counter 42→43, off-phase
+12→+13; footer v2.6 row restored — pre-existing C9 gap); PSR_v1
v3.6→v3.7 (R10.1 Блок 1 note — deptrac is a Composer dev-dep, not a
marketplace plugin, like mermaid-skill/CCPM); Pravila v1.20→v1.21
(§13.2 architecture-tooling para +deptrac); CLAUDE.md v2.6→v2.7
(§3 title, §1 row 2b, §3.3 +#43 row, §6 +para, §9 +entry, §0
cross-refs) via /claude-md-management:claude-md-improver.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:28:08 +03:00
Дмитрий 431117087f docs(arch): code-derived C4 component-layer diagram from deptrac (gap 4)
docs/architecture/c4-component-layers.md — the Level-3 layer
dependency graph generated by `deptrac analyse --formatter=mermaidjs`
(code-derived, drift-proof). Closes the A6 «C4 drift» gap at the
component level. README diagram index + regenerate note updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:15:34 +03:00
Дмитрий 5deff727a4 ci(deptrac): wire deptrac as lefthook pre-commit job 10
Job 10 runs `deptrac analyse` (root: app/) when staged app/**/*.php
changes — the layer-dependency gate. Red-green verified: a
Model→Service dependency is flagged (DependsOnDisallowedLayer,
exit 1); a clean tree exits 0. app/.gitignore += /.deptrac.cache.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:13:27 +03:00
Дмитрий 554b59359c feat(deptrac): layer model + ruleset config + ADR-005
app/deptrac.yaml — 13 layers (Controller/Service/Model/Job/…),
conservative ruleset enforcing inward/upward-violating directions.
First `deptrac analyse`: 0 violations / 481 allowed / 977 uncovered
— the codebase already conforms, so no baseline file is needed.
ADR-005 records the decision.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:09:24 +03:00
Дмитрий 507c4d869a docs(plan): deptrac architecture-fitness integration plan
9-task plan closing the 4 open A6 architecture-fitness gaps
(conformance, layer-direction, C4 drift, active design) via
deptrac as a lefthook job-10 layer-dependency gate. + cspell vocab.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:07:09 +03:00
Дмитрий f9bedb6aad build(deptrac): add deptrac 4.6.1 as a composer dev-dependency (DT1)
deptrac/deptrac ^4.6 + 5 transitive deps (symfony/config,
dependency-injection, var-exporter; phpdocumentor/graphviz;
jetbrains/phpstorm-stubs). Primary DT1 path — composer dev-dep;
no PHAR fallback needed (resolver clean, 0 security advisories).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:04:45 +03:00
Дмитрий 88eac07116 merge: origin/main (automation-graph C9 map + project-management tooling) в C9-интеграцию 2026-05-17 10:13:02 +03:00
Дмитрий b1e903f31a fix(projects): C9 code-review findings — ProjectResource отдаёт regions[] + покрытие
C1: ProjectResource не возвращал regions → edit-диалог/drawer затирали
    сохранённые регионы при сохранении. +поле в toArray().
C2: +integration-тест outbound regions[] через полный SyncSupplierProjectsJob::handle().
I1: расскип NewProjectDialog payload-теста (regions в POST).
I2: assert data.regions в ProjectsStore/UpdateTest (ловит C1 на backend-уровне).
I4: docblock — bulkUpdateRegions legacy (region_mask, не влияет на outbound до Plan 6.5).
M1: CHANGELOG v8.22 — исправлен неверный пример регионов (Москва=82).

Регрессия: Pest 905/902/3sk/0, Vitest 104f/884/3sk/0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 10:05:32 +03:00
Дмитрий ec6ebc57e0 merge: C9 — Plan 6 регионы субъект-уровня в портал
# Conflicts:
#	app/tests/Feature/Plan4/Schema/SchemaDeltaTest.php
#	db/CHANGELOG_schema.md
#	db/schema.sql
2026-05-17 09:30:21 +03:00
Дмитрий 3b7023809f feat(map): C9 nodes ccpm + product_mgmt — closes section «Управление проектами» 2026-05-17 09:10:44 +03:00
Дмитрий d733ad0a2f docs(c9): CLAUDE.md v2.6 — register C9 project-management tooling (#41-42) 2026-05-17 09:10:44 +03:00
Дмитрий 2cf7471687 docs(c9): register CCPM + product-management #41-42 (project-management category) 2026-05-17 09:10:44 +03:00
Дмитрий 6b4e7441c9 feat(c9): bootstrap docs/projects + CCPM store + ADR-004 2026-05-17 09:10:44 +03:00
Дмитрий a7b207e689 feat(c9): vendor CCPM skill into .claude/skills + lint-ignore (CP1) 2026-05-17 09:10:44 +03:00
Дмитрий 6b2da83851 docs(plan): C9 project-management tooling integration plan 2026-05-17 09:10:44 +03:00
Дмитрий cc3f2e5b13 feat(c9): enable GitHub MCP projects toolset for Projects v2 (GH1) 2026-05-17 09:10:44 +03:00
Дмитрий fad1c895a1 merge: Sprint 3E (D6/D7 — убрать placeholder-вкладки SettingsView) в портал 2026-05-17 09:03:21 +03:00
Дмитрий 1c217fae43 chore(cleanup): снять устаревший MDI clearable-workaround (CTO-19 tail) — Sprint 6 I5 2026-05-17 08:18:44 +03:00
Дмитрий 6230c0fa61 fix(a11y): aria-label с ключом на edit-кнопках AdminSystem — Sprint 6 G9 2026-05-17 08:18:44 +03:00
Дмитрий 7a537105e3 docs(polling): семантический doc-комментарий вместо списка call-site'ов — Sprint 6 F4 review-fixup 2026-05-17 08:18:44 +03:00
Дмитрий 8a7314d198 refactor(polling): вынести интервалы в constants/polling.ts — Sprint 6 F4 2026-05-17 08:18:44 +03:00
Дмитрий e41844a13b test(admin): явный stubEnv DEV=true в dev-баннер тесте — Sprint 6 B6 review-fixup 2026-05-17 08:18:43 +03:00
Дмитрий 11baaefe21 feat(admin): DEV-only баннер о застабленном auth-gate — Sprint 6 B6 2026-05-17 08:18:43 +03:00
Дмитрий 97a27fdfbf fix(a11y): focus-visible ring + keyboard-activation тест на eye-toggle — Sprint 6 A9 review-fixup 2026-05-17 08:18:43 +03:00
Дмитрий d41471c818 fix(a11y): accessible eye-toggle на полях пароля — Sprint 6 A9 2026-05-17 08:18:43 +03:00
Дмитрий 3360e6f023 docs(sprint6): implementation plan — P3 polish + cleanup tail 2026-05-17 08:18:43 +03:00
Дмитрий 7d84959c15 docs(d3): mark stale warn-only claim in D3 plan as corrected (v2.5)
The D3 plan still describes Security Guidance #40 as warn-only (the pre-correction belief). Plan body kept as a historical snapshot; added a one-line NB pointing to the v2.5 correction (Tooling §4.15 / ADR-003 / CLAUDE.md).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 07:36:26 +03:00
Дмитрий ded07d3a6b docs(d3): correct Security Guidance #40 — blocking hook, not warn-only
SG #40 was characterised across all D3 docs as warn-only / does not block. Verified end-to-end: security_reminder_hook.py does sys.exit(2) — a BLOCKING PreToolUse hook (one-time speed-bump per file+rule per session, the retry passes).

SG2: on this Windows host the bundled hooks.json hardcodes python3, absent from PATH — the hook never spawned (inert). Fixed with a python3.exe shim in the Python install dir (env-only, not in repo).

Normative sync: Tooling v2.5, PSR_v1 v3.5, Pravila v1.19, CLAUDE.md v2.5; ADR-003 amended; automation-graph sec_guidance nd(). Tool counts unchanged (40 positions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 07:29:42 +03:00
Дмитрий 608f4b2231 docs(a11): implementation plan — ML/AI tooling integration (A11)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 07:29:42 +03:00
Дмитрий 6a64a98fbf docs(a11): brainstorming spec — ML/AI tooling integration (A11)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 07:29:42 +03:00
Дмитрий f29b1b7e50 docs(5d): план Sprint 5D — cleanup mock fallback (I3/I4) 2026-05-17 07:13:51 +03:00
Дмитрий 0d2c64aa8c test(deals): T1 fixup — DealsListIntegration/KanbanRedesign под I3 (убран MOCK_DEALS-fallback) 2026-05-17 07:13:51 +03:00
Дмитрий 256acf8781 fix(admin): I4 — devPlainCode-баннер за import.meta.env.DEV 2026-05-17 07:13:51 +03:00
Дмитрий a0b1cfdcae fix(admin): I3 — убрать mock fallback в System/Tenants 2026-05-17 07:13:50 +03:00
Дмитрий 2b04bbd4f8 fix(admin): I3 — убрать mockAdmin fallback в Billing/Incidents 2026-05-17 07:13:50 +03:00
Дмитрий 888b7563cd fix(deals): I3 — убрать mock-fallback в NewDealDialog/DealDetailDrawer 2026-05-17 07:13:50 +03:00
Дмитрий 3a58090db9 test(deals): T1 review-fixup — I3-тесты через onMounted-путь 2026-05-17 07:13:50 +03:00
Дмитрий 23579dd9be fix(deals): I3 — убрать MOCK_DEALS fallback в DealsView/KanbanView 2026-05-17 07:13:50 +03:00
Дмитрий 7c12b7419c feat(map): D3 nodes — closes section «Аудит и управление рисками» 2026-05-17 06:15:30 +03:00
Дмитрий f05bb4dde2 docs(audit): CLAUDE.md v2.4 — register #39-40 audit-security (D3) 2026-05-17 06:15:30 +03:00
Дмитрий 703f101c11 docs(audit): register Trail of Bits + Security Guidance #39-40 (D3 audit-security) 2026-05-17 06:15:30 +03:00
Дмитрий 30eec9fb7d feat(audit): distill 14-phase portal audit into audit-portal skill (D3) 2026-05-17 06:15:29 +03:00
Дмитрий 83a831c46d docs(audit): toolchain attack-surface procedure + audit/ home (D3 #5) 2026-05-17 06:15:29 +03:00
Дмитрий b72780c54e feat(adr): ADR-003 — D3 audit & risk-management tooling decision 2026-05-17 06:15:29 +03:00
Дмитрий 8c9a91be1c feat(audit): customize /security-review with project FP-filter (D3 #2) 2026-05-17 06:15:29 +03:00
Дмитрий f892c94feb docs(plan): D3 audit & risk-management tooling integration plan 2026-05-17 06:15:29 +03:00
Дмитрий 21d84a77a9 style(admin): Sprint 5C — pint-fix AdminPricingTiersControllerTest 2026-05-17 05:24:44 +03:00
Дмитрий 2172d2ba45 fix(admin): G7 review-fixup — сброс effective_from при открытии редактора + boundary-тест 2026-05-17 05:24:44 +03:00
Дмитрий 915335aea6 feat(admin): G10 — браузерный confirm() удаления сетки → v-dialog 2026-05-17 05:24:44 +03:00
Дмитрий 9f791f9f93 feat(admin): G7 — выбор effective_from тарифной сетки через date-picker 2026-05-17 05:24:44 +03:00
Дмитрий c31e199e45 refactor(admin): G3 — pricing-tiers/suppliers вьюхи на типизированный api/admin.ts 2026-05-17 05:24:44 +03:00
Дмитрий 42409ddec0 feat(billing): E4 — убрать mock pending-баннер (нет платёжного шлюза до Б-1) 2026-05-17 05:24:44 +03:00
Дмитрий d667feda0f feat(billing): E2 — disabled+tooltip на кнопках Автопополнение/Сменить тариф 2026-05-17 05:24:43 +03:00
Дмитрий 6987c8a172 docs(plan): Sprint 5C — Billing/Admin (E2/E4/G3/G7/G10) 2026-05-17 05:24:43 +03:00
Дмитрий aeda3f6df1 docs(plan): A6 architecture-tooling integration plan (executed)
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>
2026-05-17 04:54:44 +03:00
Дмитрий 5cc8511990 feat(map): add adr_kit/mermaid/arch_patterns nodes — closes section A6
3 new nodes in docs/automation-graph.html (103→106 nodes, 106→109 edges):
- adr_kit, arch_patterns — plugins group
- mermaid_skill — skills_proj group (vendored skill)
All three mapped to NODE_SECTION A6 «Архитектура систем» (0→3 nodes).
NODES + NODE_DETAILS + NODE_META + 3 governing edges (psr_v1/tooling).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 04:54:44 +03:00
Дмитрий 3f91afd8d7 docs(adr): CLAUDE.md v2.3 — register #36-38 architecture-tooling (Task 7)
§3 title 35->38; §1 priority-chain row 2b 35->38; §3.3 +3 rows (#36 adr-kit, #37 mermaid-skill, #38 architecture-patterns); §3.3 footer count 35->38, architecture-tooling as the fifth off-phase subcategory; §0 cross-refs Pravila v1.16->v1.17 / PSR_v1 v3.2->v3.3 / Tooling v2.2->v2.3; §6 +2026-05-17 integration paragraph; header v2.2->v2.3.

Via /claude-md-management:claude-md-improver (CLAUDE.md §5 п.10). CHANGELOG_claude_md.md not touched — v2.1/v2.2/v2.3 are inline-only in §9 (CHANGELOG maintenance has been inline since v2.0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 04:54:44 +03:00
Дмитрий 8bedf21c08 docs(adr): register adr-kit/mermaid/architecture-patterns #36-38 in Tooling/PSR_v1/Pravila (Task 7)
Tooling Прил. Н v2.2->v2.3: new §4.11 (#36 adr-kit), §4.12 (#37 mermaid-skill), §4.13 (#38 architecture-patterns); §0 counter 35->38 formalized positions (55->58 total); new fifth off-phase subcategory 'architecture-tooling'.

PSR_v1 v3.2->v3.3: R10.1 Block 1 +2 rows (adr-kit, architecture-patterns) + Block 1 note (mermaid-skill — vendored skill). Pravila v1.16->v1.17: §13.2 +'Off-phase architecture-tooling' paragraph; PSR_v1 cross-ref v3.2+->v3.3+.

Category is non-UI -> outside R6.0/R6.1/R14 pipeline, like debug-runtime and infrastructure. CLAUDE.md §3.3 sync follows separately via claude-md-management (§5 п.10).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 04:54:44 +03:00
Дмитрий 5d5eab70fe feat(arch): seed docs/architecture — C4 Context diagram + index (Task 6)
docs/architecture/ created with README (boundary rule vs docs/adr + regeneration guide) and c4-context.md — a C4Context diagram of Лидерра: 2 actors, the system, 5 external systems (crm.bp-gr.ru, Unisender Go, Yandex 360, Sentry, JivoSite).

Smoke #3 (mermaid-skill): discoverable, authored a valid C4Context block per references/c4.md. Smoke #4 (architecture-patterns): installed + enabled + discoverable (Skills(1), Hooks(0)).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 04:54:44 +03:00
Дмитрий b7a2412e88 fix(adr): adr-judge lefthook job — Python UTF-8 mode for Cyrillic diffs
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>
2026-05-17 04:54:44 +03:00
Дмитрий dd9e37ea3f feat(adr): wire adr-judge as lefthook pre-commit job 9 (Task 5)
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>
2026-05-17 04:54:43 +03:00
Дмитрий c09b9ab7fd feat(adr): bootstrap docs/adr — ADR-000/001/002 + adr-kit guide (Task 4)
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>
2026-05-17 04:54:43 +03:00
Дмитрий 3e73c0e68f feat(arch): vendor mermaid-skill into .claude/skills + lefthook exclude (Task 3)
WH-2099/mermaid-skill (MIT): SKILL.md + 30 refs (incl. c4.md, architecture.md) + LICENSE. Standalone skill — no plugin, no hooks, no mmdc dependency; generates Mermaid source text.

lefthook markdownlint+cspell jobs get 'exclude: .claude/skills/mermaid/**' — markdownlint-cli2/cspell bypass .markdownlintignore/ignorePaths on explicit staged-file args (MK1). cspell.json + .markdownlintignore also updated for glob-mode invocations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 04:54:43 +03:00
Дмитрий 345d14d285 docs(plan): Sprint 5B — markdownlint-fix плана (MD031/MD032)
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>
2026-05-17 04:03:36 +03:00
Дмитрий bc24420ad4 style(ui): Sprint 5B — prettier-формат затронутых файлов
Регрессия full: prettier --check на 5 файлах, тронутых Sprint 5B
(T2/T3/T4). Whitespace-only, 0 изменений поведения — Vitest 67/67
на затронутых спеках. Pre-existing prettier-дрейф 28 НЕ-5B файлов
оставлен (вне scope спринта).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 04:03:36 +03:00
Дмитрий 788c7ab336 feat(ui): C6 — degradation-alert в NewDealDialog при провале загрузки списков 2026-05-17 03:48:39 +03:00
Дмитрий eb41b65dad fix(ui): C3 — сброс toast-текста + типизация теста (review-fixup) 2026-05-17 03:44:50 +03:00
Дмитрий 095032a231 feat(ui): C3 — кнопка «Экспорт» в шапке DealsView экспортирует весь список 2026-05-17 03:39:32 +03:00
Дмитрий adb5d87d1d fix(ui): B3 — ⌘K open-only + DOM-тесты палитры (review-fixup) 2026-05-17 03:33:51 +03:00
Дмитрий 8b3ea3ed2e feat(ui): B3 — минимальная ⌘K command-palette навигации 2026-05-17 03:28:05 +03:00
Дмитрий d3746406a6 docs(plan): Sprint 5B — Layout/views (B2/B3/C3/C6/C7)
План 6 задач портал-аудита Sprint 5B. T2 NAV_ITEMS поправлен 7→8
(добавлен «Импорт данных» /import — сверено с origin/main-сайдбаром).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 03:21:45 +03:00
Дмитрий 1a3a1df604 docs(ui): B2 — актуализация комментариев AppSidebar (review-fixup)
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>
2026-05-17 03:20:02 +03:00
Дмитрий 4b0809a82d feat(ui): B2 — счётчик «Сделки» в сайдбаре из API вместо хардкода 2026-05-17 03:14:13 +03:00
Дмитрий cefb71f5fa feat(api): B2 — count_only параметр на GET /api/deals 2026-05-17 03:11:03 +03:00
Дмитрий fef9499e1a docs(plan): Sprint 5A — Auth polish (A1/A4/A5/A6/A8)
План portal-audit Sprint 5 под-план A: 5 P2 UX-debt эпиков подсистемы
Auth — A1 (Yandex SSO disabled+tooltip), A4 (ResetPassword confirm
mismatch error), A5 (ForgotPassword fallback regression-тест),
A6 (TwoFactor реальный TOTP-отсчёт), A8 (DemoSeeder demo:seed + README).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:23:26 +03:00
Дмитрий 72c8cad963 fix(dev): A8 review — production-guard в DemoSeeder + точность README/теста (Sprint 5A) 2026-05-17 02:23:26 +03:00
Дмитрий aa77814206 feat(dev): A8 — composer demo:seed + README демо-данные + idempotency-тест (Sprint 5A) 2026-05-17 02:23:26 +03:00
Дмитрий fcf8626c26 fix(auth): A6 review — ранний return при redirect на /login (Sprint 5A) 2026-05-17 02:23:26 +03:00
Дмитрий be51c97dce feat(auth): A6 — реальный обратный отсчёт TOTP-окна в 2FA (Sprint 5A) 2026-05-17 02:23:26 +03:00
Дмитрий 4a1663b426 test(auth): A5 — regression generic fallback ForgotPassword (Sprint 5A) 2026-05-17 02:23:26 +03:00
Дмитрий 17d9f16b7d feat(auth): A4 — ResetPassword ошибка несовпадения паролей (Sprint 5A) 2026-05-17 02:23:26 +03:00
Дмитрий efb0dea5ed feat(auth): A1 — Yandex 360 SSO disabled + tooltip (Sprint 5A) 2026-05-17 02:23:26 +03:00
Дмитрий 120a386f05 feat(map): automation-graph — раздел «Хотелки» (отложенный backlog)
Слой WISHLIST: панель отложенных хотелок развития мозга/портала + кнопка-легенда «💡 Хотелки» в нижней легенде. Засеяно 4 хотелками раздела E8: K7-spike, мост claude-mem→ReasoningBank, claude-mem #1, двухуровневый ремонтник. Аддитивно — режим легенды наравне с «Разделы»; счётчики узлов/рёбер не меняются.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:31:26 +03:00
Дмитрий c64be74992 fix(import): final review — /import в явный список Route::view
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>
2026-05-16 20:33:15 +03:00
Дмитрий 6a3593de7a fix(import): final review — tenant-изоляция import_unknown_statuses под BYPASSRLS
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>
2026-05-16 20:31:56 +03:00
Дмитрий de066145d3 feat(import): маршрут /import + сайдбар + инструкция H9
- router/index.ts: добавлен маршрут /import (name=import, layout=app,
  requiresAuth=true, transition=ld-route-fadeup, devIndex=29)
- AppSidebar.vue: пункт «Импорт данных» (mdi-database-import-outline)
  добавлен в группу «Работа» следом за Дашборд
- router.spec.ts: TDD-кейс маршрута /import (layout=app, requiresAuth=true)
- docs/Как_перенести_данные_из_crm-bp-gr.md: инструкция H9 (4 шага + таблица ошибок)
- cspell-words.txt: добавлены формы глагола «замапить»

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 20:14:04 +03:00
Дмитрий 96cb64f33a refactor(import): Task 10 code-review — POLL_INTERVAL_MS константа
Code-review Task 10 (🟡): магическое число 2000 (интервал polling'а) вынесено
в именованную константу POLL_INTERVAL_MS — паттерн файла (как в DashboardView).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 20:08:33 +03:00
Дмитрий 59dac9be56 feat(import): ImportView — экран импорта CSV
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>
2026-05-16 20:05:15 +03:00
Дмитрий 7f05c4ab16 feat(import): api/imports.ts + UnknownStatusesDialog (wizard маппинга)
- api/imports.ts: типы ImportLogResource/UnknownStatus/StatusMapping,
  функции uploadImport/listImports/getImport/getUnknownStatuses/resolveUnknownStatuses
  (apiClient из ./client, стиль api/dashboard.ts)
- UnknownStatusesDialog.vue: wizard маппинга незамапленных статусов воронки
  (ТЗ §6.4/§6.6), 14 канонических slug, defineExpose(selection, save)
- Vitest 3/3 (tests/Frontend/UnknownStatusesDialog.spec.ts)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 19:58:33 +03:00
Дмитрий 5d64ca552e test(import): Task 9 code-review — cross-tenant тест ImportController::show
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>
2026-05-16 19:52:31 +03:00
Дмитрий a7038367e4 feat(import): ImportController + маршруты /api/imports
Task 9 Sprint 4: ImportController с 5 методами (store/index/show/
unknownStatuses/resolveUnknownStatuses), 2 FormRequest (StoreImportRequest
/ ResolveUnknownStatusesRequest), 5 маршрутов в routes/web.php под
auth:sanctum+tenant. Defense-in-depth: явный where(tenant_id) поверх RLS
(postgres superuser обходит BYPASSRLS на dev — паттерн DealController).
Тест 8/8, Larastan baseline regen (только TestCall false positives).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 19:45:47 +03:00
Дмитрий 15b53a9b2b feat(import): ImportLeadsJob — queued-обработчик CSV-импорта
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>
2026-05-16 19:31:19 +03:00
Дмитрий 952263b3e5 feat(import): Mailable ImportCompletedNotification
Task 8 — email-уведомление пользователю по завершении CSV-импорта
исторических лидов (ТЗ §6.6). Два исхода: done (счётчики строк) /
failed (сообщение об ошибке). Blade-шаблон markdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 19:23:00 +03:00
Дмитрий 5416f809a3 fix(import): Task 6 code-review — final-класс + честное имя поля errors
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>
2026-05-16 19:17:59 +03:00
Дмитрий 0b9d73018d feat(import): HistoricalImportService — идемпотентный upsert лидов
Реализован 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>
2026-05-16 19:10:23 +03:00
Дмитрий 29a4d01ff4 fix(import): Task 5 code-review — final-класс CsvLeadsParser + self::EXPECTED_COLUMNS
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>
2026-05-16 17:51:27 +03:00
Дмитрий 8f2b82405a feat(import): CsvLeadsParser + DTO ParsedLeadRow/CsvParseResult
Парсер 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>
2026-05-16 17:47:15 +03:00
Дмитрий 424987bedb feat(import): сервис StatusRuToSlugMapper (ТЗ §6.4)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:42:49 +03:00
Дмитрий ef4df2925f feat(import): сервис MonthlyPartitionManager + рефактор partitions:create-months
Выносит 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>
2026-05-16 17:37:29 +03:00
Дмитрий 8bc8c53a3b feat(import): Eloquent-модели ImportLog + ImportUnknownStatus
- ImportLog: $attributes зеркалят DB DEFAULT'ов (status/entity_type/dry_run),
  CREATED_AT/UPDATED_AT=null (таблица использует started_at/finished_at),
  casts для mapping_config (array) и dry_run (boolean)
- ImportUnknownStatus: scope unresolved() (whereNull mapped_to_slug),
  BelongsTo tenant
- Фабрики ImportLogFactory + ImportUnknownStatusFactory
- Тест ImportModelsTest (2/2, DatabaseTransactions, idempotent)
- ide-helper:models перегенерирован под новые модели
- phpstan-baseline регенерирован (квирк 25: TestCall::$tenant/$user)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:29:33 +03:00
Дмитрий 98549c52be fix(import): Task 1 code-review — убран фантомный GRANT-блок + усилен UNIQUE-тест
Code-review Task 1: явный per-table GRANT-блок для import_unknown_statuses
использовал несуществующие роли (crm_app_admin / crm_readonly). Реальные роли —
crm_app_user / crm_admin_user / crm_migrator / crm_audit_writer /
crm_supplier_worker (db/00_create_roles.sql). Блок удалён целиком из
db/02_grants.sql и db/schema.sql: import_unknown_statuses — обычная
tenant-scoped таблица, покрыта umbrella GRANT ... ON ALL TABLES +
ALTER DEFAULT PRIVILEGES (как import_log), явный per-table grant не нужен.

ImportSchemaTest: UNIQUE-тест усилен — проверяет состав колонок
(status_ru, tenant_id), а не только наличие constraint'а типа 'u'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:20:12 +03:00
Дмитрий 70f8b210f4 feat(import): H1+H2 — схема import_unknown_statuses + enrichment import_log
Sprint 4 Task 1 (schema delta §6):
- H1: новая таблица import_unknown_statuses (RLS tenant_isolation,
  UNIQUE(tenant_id,status_ru), FK→tenants/import_log/lead_statuses/users)
- H2: +5 колонок import_log (entity_type, source_system, mapping_config,
  unknown_statuses_count, dry_run)
- schema.sql v8.20→v8.21 (64 таблицы / 118 индексов / 40 RLS-политик)
- db/CHANGELOG_schema.md v8.21 entry
- db/02_grants.sql v8.21 section (crm_app_user/crm_app_admin/crm_readonly)
- migrate: hasTable/hasColumn guards (fresh-safe)
- tests: 3 Pest-теста (ImportSchemaTest) + SchemaDeltaTest v8.21 metrics
- ide-helper: _ide_helper.php + _ide_helper_models.php (были отсутствуют
  в worktree, phpstan падал молча из-за missing scanFiles entry)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:01:51 +03:00
Дмитрий 4937225da3 docs(plan): Sprint 4 — историческая миграция лидов §6 (H1-H6/H8/H9)
План CSV-импорта исторических лидов из crm.bp-gr.ru. 12 задач: schema delta
(import_unknown_statuses + enrichment import_log), сервисы парсинга/маппинга/
upsert'а, ImportLeadsJob, ImportController, frontend ImportView + wizard
маппинга статусов, маршрут /import + инструкция H9. H7 (импорт проектов)
вынесен — формат CSV проектов не специфицирован в ТЗ §6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:28:30 +03:00
Дмитрий da4d46b0d8 feat(map): automation-graph — полная актуализация по аудиту
Аудит карты против фактического состояния (~/.claude/settings.json,
project .claude/settings.json, .mcp.json, lefthook.yml, .claude/skills,
memory/). +20 узлов (83 → 103):
- плагины 5→9: +skill-creator, claude-code-setup, plugin-dev, context7
- хуки 5→12: +economy-self-check/skill-marker/skill-check/state-guard/
  postcompact/verifier (Stop) + ruflo-queen-hook
- memory 16→24: +audit_B/C, supplier_crm, full_audit_05-12/14, sprint1/2/3
- скилы проекта 2→3: +regression
Квирк 72 устранён (commit 0fa1a73) — 2 конфликта переоценены:
ag_pest↔mcp_redis BLACK→GREEN; ruflo_daemon↔ag_pest → квирки 73/77.
Все 103 узла размечены по разделам; E8 «Самообучение Claude» наполнен
(skill-creator, claude-code-setup). Топология 103 / 106 рёбер /
11 конфликтов (🔴1/3/🟢7). Smoke ✓.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:08:07 +03:00
Дмитрий f9f9fec97d feat(map): automation-graph — раздел E8 «Самообучение Claude»
+1 раздел в блок E «Мета и управление». Итого 40 разделов
(13 наполнены / 27 пусты). E8 — пустой каркас под будущий playbook.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:08:07 +03:00
Дмитрий e74e8aa6d6 feat(map): automation-graph — слой функциональных разделов (iter7)
39 разделов деятельности Лидерры (5 блоков A–E) как классификация:
все 83 узла распределены по разделам — 13 наполнены, 26 пусты
(пустые — бизнес-домены, под которые в карте dev-автоматики узлов
ещё нет). Кнопка-панель «📂 Разделы» + строка «Раздел» в Паспорте
узла. Топология карты (83/90/11) и радиальный layout без изменений.
Основа будущего «мозга»: 1 раздел = 1 playbook.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:08:07 +03:00
Дмитрий 447ef593fa feat(api): J1 — auth:sanctum+tenant middleware на /api/deals*
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:18:13 +03:00
Дмитрий 9f70d89046 feat(api): J2 — стаб-гейт EnsureSaasAdmin на /api/admin/* 2026-05-16 15:01:07 +03:00
Дмитрий 42a246d633 docs(plan): Sprint 3F — API middleware (J1/J2) 2026-05-16 14:56:11 +03:00
Дмитрий 7b04e7e752 feat(settings): D6/D7 — убрать placeholder-вкладки SettingsView
Audit findings D6/D7 (Sprint 3E): убраны 4 placeholder-вкладки
(Проекты/Команда/Интеграции/Тихие часы) из SettingsView — UI не должен
обещать неработающий функционал. Удалён PlaceholderTab.vue. Остались
4 рабочие вкладки: Профиль, Безопасность, API и Webhook, Уведомления.
Тесты: 8/8 SettingsView.spec.ts ✓, Vitest 100f/838/3sk/0 ✓.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 14:25:42 +03:00
Дмитрий 822e5346d8 docs(plan): Sprint 3E — Settings placeholder-tabs (D6/D7) 2026-05-16 14:21:56 +03:00
Дмитрий ca0c4d9318 feat(admin): G5/G6 frontend — incident detail view + РКН-notify 2026-05-16 14:09:53 +03:00
Дмитрий 3269434746 feat(admin): G6 backend — incident РКН-notify endpoint 2026-05-16 14:09:53 +03:00
Дмитрий 5e12126d71 feat(admin): G5 backend — incident detail endpoint 2026-05-16 14:09:53 +03:00
Дмитрий 8e3e06f3a4 fix(admin): G4 review — real AxiosError in error test + balance/NaN guards + a11y 2026-05-16 14:09:53 +03:00
Дмитрий c85424968e feat(admin): G4 frontend — billing row-actions menu + dialogs 2026-05-16 14:09:53 +03:00
Дмитрий 00f6611bc1 fix(admin): G4 review — lockForUpdate on refund balance + self-contained tariff tests 2026-05-16 14:09:53 +03:00
Дмитрий adabcf15a4 feat(admin): G4 backend — billing tenant actions (status/refund/tariff)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 14:09:53 +03:00
Дмитрий 3ea86d62ff docs(plan): Sprint 3D — Admin actions (G4/G5/G6) implementation plan
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 14:09:53 +03:00
Дмитрий 9a25e658b3 docs(map): automation-graph — нормативный sync под реколлаж ruflo 16.05
Карта приведена к реколлажу ruflo (Pravila v1.16 / CLAUDE.md v2.2 /
PSR_v1 v3.2 / Tooling v2.2): убраны «уровень −1», «§12 sub-policy»,
«R0 sub-policy delegation pattern».

- 4 узла-правила: лейблы v1.16/v2.2/v3.2/v2.2 + NODE_META.changed 16.05
- nd()-блоки правил: §12 — hard-rule уровня 0, R0 — головной фильтр,
  цепочка 7-уровневая (0–6), §3.5/§4.10 — advisory-подсистема
- ruflo_queen: advisory/automation-подсистема, не entry-point;
  reportsTo → Pravila §14 + CLAUDE.md §3.5/Tooling §4.10
- 4 ребра ruflo_queen→{правило} «перенял sub-policy» → flipped
  {правило}→ruflo_queen (§14 queen-триггер / §3.5 / §4.10 описывают)
- конфликт ruflo_queen↔pravila 🔴🟢 (реколлаж = правило-фикс):
  классификация 🔴2/4/🟢5 → 🔴1/4/🟢6
- §12 sub-policy → hard-rule level 0 в superpowers/hk_economy/mem_sp
  + CONFLICT hk_economy↔superpowers + EDGE_DETAILS

Топология 83/90/11 без изменений (downstream-sync, не iter).
Visual smoke 8/8 PASS (Playwright): 83 узла / 90 рёбер рендерятся,
0 JS-ошибок, легенды отредактированных узлов рендерятся корректно.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 13:44:14 +03:00
Дмитрий 73d2733522 docs(fix): claude-brain spec — битая ссылка на CLAUDE.md (../../../../../→../../../)
Pre-existing баг: 5×../ перелетал repo-root на 2 уровня. Поймана pre-push lychee реколлажа. Корректный путь от docs/superpowers/specs/ до repo-root CLAUDE.md — 3×../

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:59:42 +03:00
Дмитрий 8b9d9fb029 docs(rules): PSR_v1 R13.1 — счётчик R0.6 «8» → «10 пунктов» (после удаления п.11)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:59:42 +03:00
Дмитрий 9db66e6f27 docs(rules): Task 6 cross-consistency — вычистить остаточные R0→sub-policy cross-refs
Pravila §11.5 + §13.2 содержали живой cross-ref «PSR_v1 v3.0+, R0 → sub-policy под ruflo Queen-led routing» — после реколлажа R0 уже top-of-stack gate, формулировка стала ложной. Task 1 вычистил §13.9/§13.10, но пропустил §11.5/§13.2. + §10 v1.16-row дополнен; PSR_v1 шапка-нарратив +v3.2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:59:42 +03:00
Дмитрий 9b6fa50c4c docs(plan): ruflo hierarchy factual recollage — implementation plan
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:59:42 +03:00
Дмитрий d6f0ff868f docs(rules): CLAUDE.md v2.2 — §5 п.10 убран ruflo-routing loophole (Task 4 review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:59:42 +03:00
Дмитрий 9929b4a599 docs(rules): CLAUDE.md v2.2 — реколлаж ruflo, убран уровень −1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:59:42 +03:00
Дмитрий d84127eaa5 docs(rules): Tooling v2.2 — шапка changelog синхронизирована с §13-записью (Task 3 review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:59:42 +03:00
Дмитрий 2def31eea9 docs(rules): Tooling v2.2 — реколлаж §4.10 ruflo entry-point → advisory-подсистема
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:59:41 +03:00
Дмитрий e6556e5a97 docs(rules): PSR_v1 v3.2 — §14 cross-ref + R0.6 п.11 + опечатка (Task 2 review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:59:41 +03:00
Дмитрий 4d807fb9f2 docs(rules): PSR_v1 v3.2 — реколлаж ruflo, R0 sub-policy → top-of-stack gate
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:59:41 +03:00
Дмитрий 68f341191b docs(rules): Pravila v1.16 — §10 history row + §14.6 cleanup (Task 1 review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:59:41 +03:00
Дмитрий 91c64cde70 chore(lefthook): cspell --no-gitignore — staged-файлы под gitignored .claude/worktrees/ не проверялись
cspell.json useGitignore:true заставлял cspell игнорировать все файлы worktree, расположенного под gitignored .claude/worktrees/ (Files checked: 0 — фейковый green). Staged-файлы по определению tracked, потому --no-gitignore для pre-commit cspell безопасен и чинит worktree-коммиты.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:59:41 +03:00
Дмитрий b027a3cfee feat(reports): кнопка «Скачать» → signed download URL (F2 frontend) 2026-05-16 12:45:51 +03:00
Дмитрий ab23baa1d5 fix(reports): download/downloadUrl отклоняют expired-job по expires_at (F2 review fixup) 2026-05-16 12:42:00 +03:00
Дмитрий 086fc1a903 feat(reports): download endpoint + signed URL 24ч (F2 backend) 2026-05-16 12:36:08 +03:00
Дмитрий bd9b8e84fa feat(reports): BillingSummaryProvider + isSupported всех 4 типов (F1 закрыт) 2026-05-16 12:28:57 +03:00
Дмитрий 550e8949d6 feat(reports): SourcesSummaryProvider — агрегат сделок по utm_source (F1)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 12:21:51 +03:00
Дмитрий 4bd419654f feat(reports): ManagersSummaryProvider — агрегат сделок по менеджерам (F1)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:14:49 +03:00
Дмитрий b163d8a5ca docs(sprint3c): план Reports F1+F2 — 3 провайдера + download endpoint
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:05:58 +03:00
Дмитрий 6e35193f3b fix(deals): router в DealsViewRedesign.spec + idempotency guard + watch-test (C8/F3 review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:41:09 +03:00
Дмитрий 2504f1b9ec feat(deals): deep-link /deals?openId= из напоминаний и колокольчика (audit C8/F3)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:41:09 +03:00
Дмитрий ed61bae482 fix(dashboard): скрыть Live-бейдж при ошибке + formatRub guard + test hardening (C1 review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:41:09 +03:00
Дмитрий bf7f70a5d4 fix(dashboard): восстановить tenant-guard в load() + auth.user в тесте (C1 review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:41:09 +03:00
Дмитрий cadaecdaf8 feat(dashboard): DashboardView на real API /api/dashboard/summary (audit C1)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:41:09 +03:00
Дмитрий 283db070e1 fix(dashboard): activity-бакеты в MSK + deltaBlock для leads + test hardening (J3 review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:41:09 +03:00
Дмитрий 7705f022c1 fix(dashboard): runway_days опирается на фикс. 7д-окно, не на range (J3 review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:41:09 +03:00
Дмитрий 18f132d035 docs(rules): Pravila v1.16 — реколлаж ruflo, §12 sub-policy → hard-rule 2026-05-16 11:41:09 +03:00
Дмитрий e64eb4dbe0 feat(dashboard): GET /api/dashboard/summary — агрегат KPI/баланса/активности (audit J3)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:41:08 +03:00
Дмитрий c5261a0b22 docs(plan): Sprint 3B dashboard & deep-links implementation plan
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:41:08 +03:00
Дмитрий 425d58f2a9 docs(spec): реколлаж ruflo в иерархии — декларация vs фактический рантайм
Дизайн-спека приведения нормативки к рантайму: убрать уровень -1 «ruflo entry-point для ВСЕХ задач» (рантайм — 0 задач, рой idle, 0 enforcement); §12 Superpowers и PSR_v1 R0 → обратно hard-rule/top-gate; §14 queen-триггер сохраняется без изменений; ruflo переописывается advisory/automation-подсистемой. Утверждена заказчиком 16.05.2026.

cspell-words.txt: +реколлажирована/реколлажем/фоллбэк.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:41:08 +03:00
Дмитрий 2f267f15f7 feat(graph): iter6 — кнопки «По использованию» / «Дубли» + режим viewMode
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 10:54:11 +03:00
Дмитрий 65381f2b24 docs(audit): Sprint 3A — B1 помечен won't-do (конфликт с решением заказчика)
B4 + B5 реализованы и закрыты; B1 «Напоминания в сайдбар» откатан как
конфликтующий с прежним решением заказчика «sidebar cleanup» (5c8ad27).
Отмечено в §3 расписании, §4 таблице B и §8 approval log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 10:16:03 +03:00
Дмитрий 4a851a2d40 docs(admin): AdminLayout JSDoc — 4→7 nav-пунктов (Sprint 3A final review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 10:15:11 +03:00
Дмитрий ad9fb9dfde docs(economy): спека 5% — блок A/B3 (скоростные правила) + §5.2 актуализация
Дописана §11: 6 скоростных правил (блок A 5 пунктов + блок B п.3) внесены секцией СКОРОСТЬ в LEVELS[5] хуков; B4 (замер latency хуков) задокументирован как закрытый одноразовый bench. §5.1/§5.2 актуализированы под текущие хуки, §2 формула расширена, статус-шапка → Реализован.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 10:14:29 +03:00
Дмитрий eebcaf1912 docs+test(admin): ImpersonationBanner — убрать stale JSDoc + тест poll→render (B5 review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 10:09:29 +03:00
Дмитрий e0a3fb8d28 revert(nav): откат B1 «Напоминания» в сайдбаре — конфликт с решением заказчика
Откат a55ac9d. Audit B1 предлагал вернуть «Напоминания» в сайдбар, но
пункт был намеренно убран по требованию заказчика (commit 5c8ad27
«sidebar cleanup»; тест AppLayout.spec.ts фиксирует «Напоминания убраны
по требованию заказчика»). Решение заказчика 2026-05-16: B1 → won't-do,
пункт остаётся убранным. Восстанавливает зелёный AppLayout.spec.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 10:04:13 +03:00
Дмитрий 346c4843b0 feat(admin): ImpersonationBanner — глобальный индикатор активных сессий (audit B5)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:59:43 +03:00
Дмитрий 0fa1a7394b fix(tests): redis cache store -> array driver in test env (kill quirk 72)
SupplierPortalClient::loadSession, RefreshSupplierSessionJob, CsvReconcileJob and RouteSupplierLeadJob hardcode Cache::store('redis'), bypassing phpunit.xml's CACHE_STORE=array. Under pest --parallel every worker shares the same Memurai instance and the global supplier:session key, so one worker's afterEach forget()/flush() races another worker's mid-test loadSession() -- deterministic 1-2 failures in the tests/Feature/Supplier/ subdir-only run (quirk 72).

TestCase::setUp() repoints the redis cache store at the in-process array driver: each parallel worker gets a hermetic, worker-local cache. Production keeps the real redis driver -- the override only runs under APP_ENV=testing. New RedisCacheStoreIsolationTest guards the invariant.

Verified: tests/Feature/Supplier/ --parallel 6/6 runs 43/43 (was 42/43 +1 error); tests/Unit/Supplier/ 3/3 runs 38/38; full pest --parallel 794/791/3sk/0; Pint + Larastan clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:57:32 +03:00
Дмитрий 6e1d437f21 docs(test): AdminLayout.spec — header-комментарий 5→7 nav-items (B4 review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:48:09 +03:00
Дмитрий 9b1ac10309 feat(admin): AdminLayout nav — Тарифная сетка + Цены поставщиков (audit B4)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:43:53 +03:00
Дмитрий ffcb9b2f8e feat(graph): iter6 — «Паспорт узла» (даты + использование) в легенде
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:41:04 +03:00
Дмитрий a55ac9dee4 feat(nav): AppSidebar — пункт «Напоминания» в группе «Работа» (audit B1)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:40:00 +03:00
Дмитрий 93bfda42c9 docs(plan): Sprint 3A layout & navigation implementation plan
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:38:01 +03:00
Дмитрий 658f4be133 feat(graph): iter6 SECTION 3.6 — NODE_META + DUPLICATE_GROUPS data
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:30:49 +03:00
Дмитрий d55890bec2 fix(regression): parsePest handles JSON output from pest --parallel
pest --parallel emits a single JSON line {"tool":"pest","tests":N,"passed":N,"skipped":N,...}
instead of human-readable text; the old regex-only parser returned 0/0/0sk/0 for every
parallel run. Added JSON-first branch with regex fallback; 3 new unit tests cover the
JSON path (passed+skipped, with failures, no skipped field).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:20:47 +03:00
Дмитрий d9ce953e53 docs(economy): спецификация и план уровня «экономия 5%»
Уровень «экономия 5%» = «0% без избыточности»: то же качество, что 0%,
вырезаны 6 пунктов дублирующей/бесполезной работы (re-read CLAUDE.md,
тесты-после-каждой-правки, gitleaks-full-history per-commit, Stop-верификатор,
авто-гейты brainstorming/plan-mode -> §12.2-floor). Уровень 0% не меняется.

cspell-words.txt: +коммитятся (валидная форма семейства коммит*).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:15:42 +03:00
Дмитрий 0465b91cac docs(regression): SKILL.md — list RED-INCOMPLETE verdict + exit codes (doc review)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:04:48 +03:00
Дмитрий 1405e00f4c feat(regression): SKILL.md — skill doc + invocation rules
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:51:56 +03:00
Дмитрий bd27047aad docs(rls): document rls-check skill <-> rls-reviewer agent boundary
Both tools check RLS compliance; the boundary "когда какой" was
undocumented (tracked as a RED conflict on the automation graph).

- .claude/skills/rls-check/SKILL.md: +section "Граница с агентом
  rls-reviewer", +bullet in "Не использовать когда", +clause in
  the frontmatter description.
- .claude/agents/rls-reviewer.md: +mirrored section "Граница со
  скилом /rls-check", +bullet in "Out of scope", +clause in
  the description.
- docs/automation-graph.html: conflict sk_rls<->ag_rls recolored
  RED->GREEN (CONFLICT edge + both nd() node entries + EDGE_META).
- cspell-words.txt: +1 pre-existing word surfaced by the cspell
  full-file scan of the now-staged SKILL.md.

Rule: one named table -> /rls-check; diff/branch/PR -> rls-reviewer.
The smoke test stays skill-only by design (running Pest in a review
subagent is slow and hits --parallel quirks 72/77).

Spec:  docs/superpowers/specs/2026-05-16-rls-tooling-boundary-design.md
Plan:  docs/superpowers/plans/2026-05-16-rls-tooling-boundary.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:48:30 +03:00
Дмитрий db8aa06f52 fix(regression): detect Windows cmd.exe "is not recognized" as missing binary
A missing cmd-based tool on Windows exits 1 with an "is not recognized"
message, not POSIX exit 127. runCheck now also matches that message so a
missing composer/npm is classified SKIPPED (verdict RED-INCOMPLETE) per
spec §8, instead of a plain failure. Code-review follow-up for Task 7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:48:25 +03:00
Дмитрий 9fd1d7cdf5 feat(regression): runCheck I/O layer + main orchestrator
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:41:22 +03:00
Дмитрий ee4969dffa feat(regression): 12-check registry (quick=6, full=12)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:31:55 +03:00
Дмитрий c9672e81e6 fix(billing): TopupDialog NaN-guard + state reset on open (Task 5 review)
Code-quality review fixups: Number.isFinite-guard в amountError/canSubmit
(очищенное number-поле не должно включать кнопку); watch(model) сбрасывает
amount/errorMsg при открытии (паттерн ReminderDialog, нет префилла/race);
комментарий про намеренный refetch в onTopupSuccess; flushPromises в spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:29:05 +03:00
Дмитрий e81cb5a1e5 feat(regression): canonical line / row / verdict formatters
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:26:45 +03:00
Дмитрий c2cb3af4c6 feat(billing): TopupDialog + Пополнить wiring (E1)
TopupDialog (сумма + пресеты + min 100 ₽ валидация) → POST
/api/billing/topup. Кнопки «Пополнить баланс» (шапка) и «Пополнить»
(BalanceCard) открывают диалог; при успехе — refresh кошелька +
транзакций + snackbar.

Sprint 2 Plan C, audit E1 (frontend).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:16:08 +03:00
Дмитрий 4a7c7cdddf feat(regression): GREEN/RED/RED-INCOMPLETE verdict logic
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:14:40 +03:00
Дмитрий 47f83dac12 docs(plan): RLS tooling boundary implementation plan
8-task plan for the rls-check skill <-> rls-reviewer agent boundary:
mirrored "Граница..." sections in both tool files, conflict recolor
RED->GREEN on the automation graph (4 spots), lint sweep, Playwright
visual smoke, one atomic commit, memory sync.

Spec: docs/superpowers/specs/2026-05-16-rls-tooling-boundary-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:13:16 +03:00
Дмитрий 5bc6a029f2 docs(plan): automation-graph iter6 — node meta + duplicates implementation plan
4-task plan for iter6 of docs/automation-graph.html: «Паспорт узла»
legend section (since/changed/uses) for all 83 nodes + 2 footer toggle
buttons (usage heatmap, duplicate highlight). NODE_META (83 records) and
DUPLICATE_GROUPS (6 pairs D1-D5/D7) carry factual values derived from
76 session transcripts (window 09-16.05.2026) + git history; method and
raw outputs in Appendix A. cspell-words.txt += pcreator, pvalid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:11:57 +03:00
Дмитрий 0ef093f7c5 fix(billing): InvoicesTable has_pdf disabled test + formatter doc (Task 4 review)
Code-quality review fixups: тест на :disabled PDF-кнопки по has_pdf
(spec-mandated поведение без покрытия); doc-комментарий billingFormatters
дополнен InvoicesTable в списке потребителей.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 08:07:57 +03:00
Дмитрий ac2c794542 feat(billing): TransactionsTable + InvoicesTable real API (E3)
TransactionsTable — server-driven история транзакций (GET
/api/billing/transactions, табы → фильтр type). InvoicesTable —
GET /api/billing/invoices с empty-state (real-but-empty до Б-1).
billingFormatters почищен (drop status/format-функций), mockBilling
ужат до pending-баннера (E4).

Sprint 2 Plan C, audit E3 (frontend pt2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:56:22 +03:00
Дмитрий f924e4413c feat(regression): Vite build / Larastan / gitleaks / lychee parsers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:55:35 +03:00
Дмитрий 21debac6c4 docs(spec): RLS tooling boundary — граница rls-check скил ↔ rls-reviewer агент
Дизайн-спек устранения конфликта 🔴 RED #1 карты автоматизации:
скил /rls-check и агент rls-reviewer оба проверяют RLS без чёткой
границы «когда какой».

Решение (Подход 1 — асимметрия как граница): оставить оба, прописать
регламент. Скил — одна названная таблица + живой дымовой тест;
агент — diff/ветка/PR, только 7 статических проверок. Дымовой тест
намеренно вне агента (Pest в ревью-субагенте медленный + задевает
квирки 72/77).

Затрагивает только проектно-локальные файлы инструментов + карту —
0 правок нормативки (Pravila/CLAUDE.md/PSR_v1/Tooling).

cspell-words.txt: +скиле +скилом (падежные формы термина «скил»).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:51:57 +03:00
Дмитрий b2f12cbe06 feat(regression): Pest + Vitest count parsers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:45:32 +03:00
Дмитрий 2723261033 fix(billing): clear stale wallet on retry + retry-button test (Task 3 review)
Code-quality review fixups: loadWallet() catch-блок сбрасывает wallet в
null (нет ложного рендера устаревших данных при неудачном повторе);
тест на кнопку «Повторить» (re-fetch + переход в success-состояние).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:44:19 +03:00
Дмитрий fe9ac213b7 feat(regression): skill scaffold + resolveBinary/buildHeader/parseExit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:40:21 +03:00
Дмитрий 112cdc82cd docs(plan): /regression skill — implementation plan (writing-plans)
9-task TDD plan implementing docs/superpowers/specs/2026-05-16-regression-skill-design.md: run.mjs split into exported pure functions (resolveBinary, parsers, computeVerdict, formatters, CHECKS registry) + main orchestrator; co-located run.test.mjs (node:test — 36 unit tests + unknown-arg subprocess test, ruflo-queen-hook.test.mjs pattern); SKILL.md; functional verification per spec §10.

Next: subagent-driven-development or executing-plans.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:33:56 +03:00
Дмитрий cc624543e9 feat(billing): BillingView wallet + BalanceCard real API (E3)
api/billing.ts (getWallet) + BillingView тянет GET /api/billing/wallet
на mount (шапка + BalanceCard, loading/error-state). BalanceCard на
реальные props с nullable-тарифом. featureLabel для feature-слагов.

Sprint 2 Plan C, audit E3 (frontend pt1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:32:59 +03:00
Дмитрий 00937b7765 docs(spec): automation-graph iter6 — dates + usage + duplicates design
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:32:35 +03:00
Дмитрий e822925ded docs(spec): /regression — amend §10, add run.test.mjs (writing-plans)
Spec §10 claimed run.mjs needs no unit harness, on the false premise that tools/*.mjs have no tests. In fact all 3 tools/*.mjs have a co-located .test.mjs (node:test). Amended §2/§3/§4/§10 + header note: run.mjs is split into exported pure functions (parsers, verdict, canonical-line, platform fork) + orchestrator, with a co-located run.test.mjs (node:test, ruflo-queen-hook.test.mjs pattern) — pure functions unit-tested, main subprocess-tested.

Aligns the spec with the economy-0% TDD mandate and the project tools/*.mjs convention before writing the implementation plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:25:10 +03:00
Дмитрий 65c5178c29 fix(billing): runwayDays clamps negative balance to 0 + type-filter test (Task 2 review)
Code-quality review fixups: runway_days клампится в 0 при отрицательном
балансе (overdrawn-тенант не должен показывать «−N дней»); (int)-каст в
wallet() для консистентности; усилены assertJsonPath на type-фильтре.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:24:34 +03:00
Дмитрий 5e6b1b651a docs(spec): /regression skill design — canonical regression sweep
Brainstorming-phase design for custom skill #1 from claude-automation-recommender: a /regression skill packaging the project regression sweep (Pest --parallel, Vitest, Larastan, vue-tsc, lint/format, lychee, gitleaks) into one invocation — two tiers (quick/full), bundled .mjs orchestrator, canonical status line, GREEN/RED exit-code verdict.

Q1-Q4 design forks approved via brainstorming; spec self-review passed. cspell-words.txt: +6 project glossary transliterations introduced by the spec. Next: superpowers:writing-plans for the implementation plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:15:19 +03:00
Дмитрий 040d25423d feat(billing): wallet/transactions/invoices read API (E3)
GET /api/billing/wallet (баланс + тариф + runway), /transactions
(пагинированный balance_transactions с фильтром type), /invoices
(saas_invoices, real-but-empty до Б-1). TariffPlan модель +
Tenant::tariff() relation + BalanceTransactionFactory.

Sprint 2 Plan C, audit E3 (backend).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:08:09 +03:00
Дмитрий 7bee35768d fix(billing): topup save() rationale comment + cross-tenant test (Task 1 review)
Code-quality review fixups: документирующий комментарий про безопасность
Eloquent save() для bcmath-строки (расхождение с LedgerService raw-update);
cross-tenant isolation тест на /api/billing/topup; balance_rub_after в
assertDatabaseHas.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 07:00:19 +03:00
Дмитрий c4370f6a2c refactor(graph): ruflo cluster factual recollage — 9 nodes / 16 edges (iter5)
iter4 нарисовал блок ruflo как Queen-led рой из 9 специализированных
ролей — декларация, не рантайм. iter5 приводит блок к фактической
инспекции рантайма 15.05.2026.

- -7 фиктивных ролей (Architect/Coder/Security/RLS/QA/Tester/Reviewer)
- +5 фактических узлов (10 воркеров idle, recall-хук, каталог агентов
  100 определений, slash-команды 88, плагины 0 из 20)
- рёбра 22 -> 16: убраны 3 фиктивных делегирующих ребра
- конфликт daemon<->mem_state перенацелен на memory<->mem_state
- двустороннее отображение конфликтов: правки pravila/mem_state/ag_pest
- метрики: 85->83 узла, 96->90 рёбер, 11 конфликтов без изменений

Spec: docs/superpowers/specs/2026-05-15-automation-graph-iter5-ruflo-factual-design.md (efd588f)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 06:48:46 +03:00
Дмитрий 44dc1025ec feat(billing): topup ledger service + POST /api/billing/topup stub (E1)
BillingTopupService кредитует tenants.balance_rub (bcmath) и пишет
append-only строку balance_transactions(type='topup'). BillingController
+ route POST /api/billing/topup под [auth:sanctum, tenant]. MVP-stub:
без платёжного шлюза (ЮKassa — post-Б-1).

Sprint 2 Plan C, audit E1 (backend).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 06:46:52 +03:00
Дмитрий c46d6264f3 docs(plan): Sprint 2 Plan C — Billing E1/E3 (writing-plans)
5-task план реализации audit-эпиков E1 (TopupDialog + POST
/api/billing/topup stub) и E3 (BillingView Overview на real API:
wallet/transactions/invoices). Backend: BillingController +
BillingTopupService + TariffPlan. Frontend: api/billing.ts + 4
компонента биллинга с mock на real API.

Sprint 2 Plan C. Источник: docs/superpowers/specs/2026-05-15-portal-audit-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 06:40:11 +03:00
Дмитрий 6819238508 docs(plan): automation-graph iter5 — ruflo factual recollage plan
План реализации iter5 поверх spec efd588f: 2 задачи (реколлаж кластера
ruflo в automation-graph.html одним атомарным коммитом + синхронизация
memory). Полное литеральное содержание узлов/рёбер/деталей, верификация
через grep + visual smoke. +2 слова в cspell-words.txt (арг, греп).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 06:28:58 +03:00
Дмитрий c693d03a75 test(settings): ApiTab — load error-path coverage + idiomatic disabled check (review M2/M3)
Code-quality review of Task 5: adds tests for the loadApiKey/loadWebhook
catch branches (apiKeyError/webhookError -> error v-alert) and changes
the Copy-button disabled check to the idiomatic falsy form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 22:35:49 +03:00
Дмитрий 298a7fa9de feat(settings): ApiTab wired to api-key + webhook endpoints (closes D2-D5)
Audit D2/D3/D4/D5: all four ApiTab buttons were handler-less and the
fields were hardcoded. Adds api/apiKeys.ts + api/webhooks.ts modules and
rewires ApiTab: loads the api-key prefix + webhook settings on mount;
Copy -> clipboard + snackbar; Regenerate -> confirm dialog -> POST
regenerate (full key shown once); Save Webhook -> PUT webhook-settings;
Test Webhook -> POST test with the result in a snackbar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 22:27:56 +03:00
Дмитрий dc9cab300c test(api): WebhookSettings — tenant-isolation + failure-path coverage (review M2/M3/M4)
Code-quality review of Task 4: adds a cross-tenant isolation test
(verifies the where(tenant_id) guard, matching ApiKeyControllerTest)
and a test()-endpoint failure-path test (HTTP 500 -> ok=false). Drops
the @return docblock from OutboundWebhookSubscriptionFactory for
consistency with ApiKeyFactory, eliminating a baseline entry at source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 22:21:52 +03:00
Дмитрий 3266909346 feat(api): outbound webhook settings endpoints (closes J5 part 2)
Audit J5/D4/D5: the outbound_webhook_subscriptions table existed in
schema but had zero code. Adds the OutboundWebhookSubscription model +
factory and WebhookSettingsController with GET/PUT
/api/tenants/me/webhook-settings (one subscription per tenant; secret
generated + returned once on creation, bcrypt-hashed) and POST
/api/webhooks/test (unsigned connectivity check — HMAC-signed event
delivery is a separate post-MVP epic). Tenant-scoped via auth:sanctum +
tenant middleware.

phpstan-baseline.neon: additive-only entries for new test file
(Pest\PendingCalls\TestCall false-positives — documented project pattern)
and OutboundWebhookSubscriptionFactory method.childReturnType (same
pattern as ProjectFactory/TenantFactory/UserFactory already in baseline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 22:13:32 +03:00
Дмитрий a26f5af2da refactor(api): ApiKeyController index() excludes expired keys (review M1)
Code-quality review of Task 3: index() filtered by is_active only —
an expired-but-active key would be listed as valid. Adds an
expires_at > now() filter plus a test. Cannot occur today (regenerate
is the only write path, always +1 year) but is the correct semantic
contract for an «active key» listing.

phpstan-baseline.neon: count bumps only for ApiKeyControllerTest.php
($tenant 5→7, $user 3→5, getJson 3→4).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 22:06:14 +03:00
Дмитрий a5e2bbbbe8 feat(api): api_keys model + GET/regenerate endpoints (closes J5 part 1)
Audit J5/D3: the api_keys table existed in schema but had zero code.
Adds the ApiKey model + factory, and ApiKeyController with GET
/api/api-keys (list active keys, key_hash hidden) and POST
/api/api-keys/regenerate (deactivate prior + create new, full key
returned once, bcrypt-hashed in DB). Tenant-scoped via auth:sanctum +
tenant middleware (RLS on api_keys). phpstan-baseline.neon updated for
Pest PendingCalls false-positives in the new test file; also removes
8 pre-existing stale ignore.unmatched entries (properties now resolved
by existing @mixin IdeHelper* docblocks — confirmed pre-existing via
git stash test before Task 3 changes).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:53:35 +03:00
Дмитрий 2c59a00714 refactor(settings): ProfileTab — document auth-guard assumption + tighten spec (review M1/M2)
Code-quality review of Task 2: documents why ProfileTab needs no
watch-resync of auth.user (router beforeEach awaits fetchMe before
requiresAuth navigation); tightens the save-error test to assert the
exact fallback message instead of mere truthiness.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:44:17 +03:00
Дмитрий 075a661c62 feat(settings): ProfileTab wired to PATCH /api/auth/me (closes D1)
Audit D1: ProfileTab fields were hardcoded refs and the Save button had
no handler. Rewired to the auth store + a new api/auth updateProfile()
calling PATCH /api/auth/me. Single «Полное имя» field split into Имя +
Фамилия (matches users.first_name/last_name); decorative «Роль» field
removed (no such column). AuthUser type gains phone + timezone.

SettingsView.spec.ts updated: «Полное имя» assertion changed to check
for «Имя» and «Фамилия» (collateral fix for the intentional field split).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:37:40 +03:00
Дмитрий b40a76e0ff test(auth): UpdateProfileTest — 422 coverage for empty last_name (review M1)
Code-quality review of Task 1: first_name had a 422 test but last_name
(identical required rule) did not. Adds the symmetric test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:30:13 +03:00
Дмитрий f23a71b670 fix(test): pin SyncSupplierProjectsJobTest clock before 20:55 MSK cutoff
SyncSupplierProjectsJob:77 has a time-budget guard that breaks the
sync loop after 20:55 Europe/Moscow. Five of the eight tests in
SyncSupplierProjectsJobTest omitted Carbon::setTestNow(), so they
inherited real wall-clock time and silently failed (job no-ops)
every evening after 20:55 MSK -- a latent test bug since dedaae5
(Plan 3), mis-attributed to a Redis race (quirk 72) in earlier audits.
Pins beforeEach to a fixed pre-cutoff clock; the job code is correct
and unchanged. Verified: 8/8 in isolation, full suite back to green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:21:36 +03:00
Дмитрий d8d2f37598 feat(auth): PATCH /api/auth/me profile update endpoint (closes J6)
Audit J6: ProfileTab needs a full-profile update endpoint. Adds
AuthController::updateProfile (first_name/last_name/phone/timezone),
routed in the existing /api/auth auth:sanctum group; mirrors the
sibling updateNotificationPreferences. userResource() now also returns
phone + timezone so the GET /me round-trip carries them.

phpstan-baseline.neon updated for Pest PendingCalls false positives
in the new test file (same pattern as all other Feature test files).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 20:58:00 +03:00
Дмитрий 772cdf4109 docs(plan): Sprint 2 Plan B — Settings (D1-D5 + J5 + J6)
Plan B of the Sprint 2 split — the Settings subsystem, 5 atomic TDD
tasks: PATCH /api/auth/me profile endpoint (J6); ProfileTab rewired to
real API (D1); ApiKey model + api-keys endpoints (J5/D3); outbound
webhook settings endpoints (J5/D4/D5); ApiTab full wiring (D2-D5).
Schema delta = 0 — api_keys + outbound_webhook_subscriptions tables
already exist in schema.sql.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 20:51:46 +03:00
Дмитрий 61e1cffb98 fix(auth): LegalDocView v-alert role=note + trim back-link whitespace (review M-1/M-2)
Code-quality review of the legal stub pages: the always-present
informational v-alert defaulted to role=alert (assertive live-region) —
changed to role=note for a static advisory (WCAG 2.1 AA). Trimmed
cosmetic whitespace inside the back-link element.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:46:09 +03:00
Дмитрий 012053a783 feat(auth): /legal/offer + /legal/privacy stub pages (closes A7)
Audit A7: the «Оферта» / «Политика» links in the AuthLayout footer were
raw <a href> pointing at unrouted paths -> 404 via the SPA catch-all.
Adds a single DRY LegalDocView served by /legal/:doc(offer|privacy),
rendering an honest «document being finalized» stub (real legal text
needs юр. редактура — реестр K3 / blocker Б-1). Footer links upgraded
to <RouterLink> for SPA navigation. Also refreshes two stale auth-layout
doc-comments left by the /recovery removal (review M1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:37:20 +03:00
Дмитрий 70508b6675 refactor(auth): remove orphaned /recovery RecoveryCodesView page (closes A2, A3)
Audit A2/A3: RecoveryCodesView (route /recovery) had a TODO no-op
continue handler and 8 hardcoded mock codes. Recon found the page is
orphaned — nothing in the UI navigates to /recovery. The real 2FA
recovery-codes flow lives entirely in Settings -> Безопасность
(TwoFactorCard setup wizard + RecoveryCodesCard regeneration), both
already wired to the real API. Per user decision (2026-05-15) the
orphan is deleted rather than polished.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:26:26 +03:00
Дмитрий 7a4f8c2793 docs(plan): Sprint 2 Plan A — Auth (A2/A3 orphan delete + A7 legal pages)
Sprint 2 (P1 wave 1) split into 3 sub-plans per writing-plans
scope-check (Auth / Settings / Billing — independent subsystems).
Plan A covers the Auth subsystem:
- A2/A3: delete orphaned /recovery RecoveryCodesView (real flow lives
  in Settings -> Безопасность; user-approved deletion 2026-05-15).
- A7: /legal/offer + /legal/privacy stub pages via one DRY LegalDocView.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:21:30 +03:00
Дмитрий 1fd6f7f597 fix(security): harden impersonation/webhook/tenant — audit A2/A3/B3/C2
- A2: impersonation _dev_plain_code в ответе init только в local/testing
- A3: X-Tenant-Id принимается только в local/testing (anti-spoof тенанта)
- B3: WebhookReceiveController isHmacRequired() default false→true (fail-secure)
- C2: SupplierWebhookController per-IP rate-limit 600/min (DoS-guard)
- WebhookReceiveTest обновлён под B3 (отсутствие настройки → 401)

Tests: 70/70 passed (323 assertions) — Webhook/Impersonation/Tenant suites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:16:13 +03:00
Дмитрий cc7ec0d749 docs(tooling): sync v2.1 header version + history row (review) 2026-05-15 17:59:50 +03:00
Дмитрий 8b47aa5a4d docs(sync): PSR_v1 v3.1 + Tooling v2.1 — §14 queen-trigger cross-ref 2026-05-15 17:53:44 +03:00
Дмитрий fff25605d0 fix(claude_md): §1 two explicit hard-rules + §0 v1.15 cell relabel (review) 2026-05-15 17:50:21 +03:00
Дмитрий 2722f60420 docs(claude_md): §14 queen-trigger refs — §1/§3.5/§0 (v2.1) 2026-05-15 17:44:12 +03:00
Дмитрий 3b8a5184c7 fix(pravila): §0 — clarify §12/§14 non-conflict (review) 2026-05-15 17:39:40 +03:00
Дмитрий efd588f661 docs(spec): automation-graph iter5 — ruflo factual recollage design
Design spec for reworking the ruflo cluster on docs/automation-graph.html
to reflect factual runtime state (live MCP + filesystem inspection)
instead of the normative declaration: 7 fictional swarm roles removed;
real footprint added as summary nodes (100-agent catalog, 88 slash
commands, recall hook, "0 plugins / 0 skills" node); broken daemon
(spawn claude ENOENT) and idle hive (0 tasks) made explicit; fictional
delegation edges dropped. Map 85->83 nodes / 96->90 edges / 11 conflicts.

cspell-words.txt: +10 terms (Russian inflections + ENOENT).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:35:59 +03:00
Дмитрий 1f9b9ab788 docs(pravila): +§14 Ruflo Queen routing hard-rule (v1.15) 2026-05-15 17:34:00 +03:00
Дмитрий fb883148b6 feat(ruflo): register queen-trigger hook in .claude/settings.json 2026-05-15 17:28:21 +03:00
Дмитрий 22056baabc fix(ruflo): queen-hook isDiscussion — word-boundary guard (review) 2026-05-15 17:25:09 +03:00
Дмитрий dc6caea99f feat(ruflo): queen-trigger UserPromptSubmit hook (TDD) 2026-05-15 17:17:27 +03:00
Дмитрий d21b6556d2 docs(plan): ruflo queen-trigger + delegation implementation plan 2026-05-15 17:12:12 +03:00
Дмитрий cce3baea49 docs(spec): ruflo queen-trigger + delegation hard-rule design
Brainstorming output: trigger words queen/королева -> unconditional ruflo
Queen routing (Pravila §14, new explicit hard-rule), enforced via
tools/ruflo-queen-hook.mjs UserPromptSubmit hook. Broader: proactive
ruflo-spawn proposal for non-trivial tasks. Cost-gate: preview + confirm
before paid hive-mind spawn --claude.

cspell-words.txt: +9 Russian IT-slang inflections for the spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:00:05 +03:00
Дмитрий 0f94c21332 fix(docs): Tooling_v8_3 — битую ссылку на удалённый форк → backtick-спан
Удаление docs/automation-graph-ruflo.html (automation-graph iter4, d18b60f)
оставило битую markdown-ссылку в §«Связано». Конвертирована в backtick-спан
(как упоминания того же форка в CLAUDE.md) + нота «влит и удалён» —
исторический факт сохранён, pre-push lychee проходит.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:37:44 +03:00
Дмитрий d18b60f4ae chore(graph): remove automation-graph-ruflo.html fork — merged into main map (iter4)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:06:16 +03:00
Дмитрий fcdd5b5f14 docs(graph): refresh rule nodes + §12/sub-policy to v2.0 normative (iter4)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:56:36 +03:00
Дмитрий 6c3640c45b docs(plan): ruflo H7 implementation addendum — onnxruntime dedupe
Records the key divergence found during subagent-driven execution: the
H7 fix needed onnxruntime-node dedupe in addition to the getBridge patch
(two incompatible onnxruntime-node versions => DLL collision). Documents
3 residual ruflo-alpha quirks and the post-ruflo-update re-apply step.

cspell-words.txt +dlopen (ERR_DLOPEN_FAILED token).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:50:11 +03:00
Дмитрий 21f81ed6ea fix(graph): ruflo delegation nodes — legacy refs to manages slot (iter4 Task 1 review)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:48:22 +03:00
Дмитрий cd04cd6336 feat(ruflo): register UserPromptSubmit advisory recall hook
Wire tools/ruflo-recall-hook.mjs into .claude/settings.json so ruflo
memory recall is injected per prompt. Project-scoped, fail-open.
Absolute path (forward slashes) — robust vs Windows shell var expansion.
Verified: hook recalls stored entries, ~1.55s latency (under 3500ms cap).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:45:54 +03:00
Дмитрий 7e87324dde feat(graph): ruflo orchestrator overlay — +12 nodes / +22 edges / 3 conflicts (iter4)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:34:39 +03:00
Дмитрий 08d5ff1151 feat(ruflo): UserPromptSubmit advisory recall hook
Hook script that runs ruflo memory search per prompt and injects top
matches as additionalContext. Fail-open (error/timeout -> empty inject,
exit 0, never blocks). Pure-function core unit-tested via node --test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:34:33 +03:00
Дмитрий ef71bce0a2 feat(ruflo): ruflo-h7-patch.mjs also dedupes onnxruntime-node
The H7 fix needs two operations on the global ruflo install: the
getBridge() patch AND disabling the duplicate nested onnxruntime-node
(@xenova/transformers' 1.14.0 vs the hoisted 1.24.3 — DLL name collision
=> ERR_DLOPEN_FAILED). The re-apply script now does both so the whole
fix survives a ruflo update.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:28:18 +03:00
Дмитрий b8ef4a0a7e docs(plan): automation-graph iter4 — ruflo big-bang merge implementation plan
5 задач: ruflo-наслой (12 узлов / 22 ребра / 3 конфликта) → рефреш под
нормативку v2.0 (4 узла-правила + §12/sub-policy) → удаление форка →
visual smoke → синхронизация memory. 3 атомарных коммита реализации.
cspell-words.txt +2 словоформы (тулбар/скила).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:24:39 +03:00
Дмитрий be755dd8eb fix(ruflo): harden ruflo-h7-patch.mjs — argv guard + unknown-flag rejection
Code-review fixes: guard pathToFileURL against undefined argv[1];
reject unrecognised flags with exit 2 before any filesystem access
(prevents a --revert typo from silently applying the patch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:36:56 +03:00
Дмитрий 1052ddfc97 feat(ruflo): H7 patch re-apply script (getBridge -> null)
Idempotent script that forces @claude-flow/cli getBridge() to return null
so ruflo memory ops use the persisting raw sql.js path. Pure-function core
unit-tested via node --test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:27:14 +03:00
Дмитрий c6c6e8c0cc docs(plan): ruflo memory H7 fix + advisory hook — implementation plan
8-task plan for the approved design (spec a6649e4):
- D1 (Tasks 1-3): install standalone claude CLI, verify spawn --claude
- D2 (Tasks 4-5): TDD ruflo-h7-patch.mjs, apply patch, verify round-trip
- D3 (Tasks 6-7): TDD ruflo-recall-hook.mjs, register UserPromptSubmit hook
- Task 8: memory update + push

cspell-words.txt +3 entries used by the plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:15:14 +03:00
Дмитрий a6649e4696 docs(spec): ruflo memory H7 fix + advisory hook — design
Design for 3 deliverables (brainstorming output):
- D1: install standalone claude CLI to unblock hive-mind spawn --claude
- D2: fix H7 memory-persistence bug — patch getBridge() so memory ops
  use the persisting raw sql.js path instead of the non-flushing
  AgentDB-v3 bridge
- D3: UserPromptSubmit advisory hook injecting ruflo memory recall

cspell-words.txt +11 Russian IT-slang inflections used by the spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:00:47 +03:00
Дмитрий 55696e5b40 docs(spec): automation-graph iter4 — ruflo big-bang merge design
Дизайн слияния ruflo-наслоя в каноническую docs/automation-graph.html.
Решения brainstorming: одна карта (форк удаляется), честный гибрид
(Queen уровнем −1 + конфликт-маркер «декларация ≠ parallel subsystem»),
полный рефреш под нормативку v2.0. +12 узлов / +22 ребра / 8→11 конфликтов.
cspell-words.txt +4 словоформы (форке/наслой/нормативке/рефреш).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 13:25:37 +03:00
Дмитрий 5ac2961698 feat(ruflo): activate runtime — daemon-as-service + hive-mind + real embeddings
Полная активация ruflo runtime (заказчик: «Активировать ruflo runtime» →
«Полная (daemon-as-service + hive-mind spawn)»). Закрывает paper/runtime gap
из Phase 3-4 нормативной инверсии.

Что активировано:
- ruflo установлен глобально (npm i -g ruflo — стабильное дерево вместо
  ephemeral npx-cache; решает module-resolution для embeddings)
- Daemon ACTIVE под PM2 (ruflo-daemon, 5 workers); reboot-survival через
  Windows Task Scheduler PM2-ruflo-daemon (pm2 resurrect onlogon — ruflo
  native install-supervisor только launchd/systemd, pm2-windows-service
  deprecated+broken non-interactive → Task Scheduler fallback)
- Hive-mind ACTIVE — Queen-led (hierarchical-mesh/byzantine) + 9 worker-агентов
- Memory init — sql.js .swarm/memory.db + реальные embeddings
  Xenova/all-MiniLM-L6-v2 384-dim (sharp/libvips fix: @xenova/transformers
  hard-deps sharp, prebuilt libvips timeout'ит — curl tarball в npm-cache/_libvips)

Repo-изменения:
- .gitignore +.swarm/ +ruvector.db (ruflo runtime state, не tracking)
- CLAUDE.md §3.5 + §6 «Runtime state» — paper-level → runtime active
- Tooling §4.10 «Runtime state» — аналогичный sync

Verification:
- Phase A gate: Pest 742/739/3sk/0 + Vitest 92f/774/3sk/0 + vue-tsc 0 ✓
- ruflo doctor: 10 passed / 7 warnings (alpha/optional)
- Pest --parallel post-activation: 0 регрессий от ruflo. 1 intermittent error
  классифицирован pest-parallel-debugger агентом (11 runs) как quirk 72 —
  ruflo grep-подтверждённо не трогает Redis :6379, worker-jitter лишь
  усиливает частоту pre-existing flake (suite green 739/739/0/3 5× verified)

Известные alpha-bugs (документированы):
- ruflo memory store CLI не персистит между invocations (in-memory sql.js)
- daemon worker-jitter усиливает Pest quirk 72 — пауза pm2 stop ruflo-daemon
  на baseline regression
- $-расход near-zero: ruflo doctor «No API keys found», daemon не делает
  платных LLM-вызовов; cap $10/день в .env.local + PM2 env как belt

Daemon-resurrect helper: C:\Users\Administrator\ruflo-daemon-resurrect.cmd
(machine-level, вне репо). Effective state: runtime активен.

Related: ruflo big-bang Phase 3-4 нормативная инверсия (9c3057b/d30cbeb/
5df88a1/f65a8d7/6287561), spec/plan 2026-05-15.
2026-05-15 12:31:53 +03:00
Дмитрий 6287561fce docs(sync): Phase 4 cross-refs sync + CHANGELOG_claude_md.md +v2.0 entry — ruflo big-bang Day 4
Ruflo big-bang Phase 4 Task 4.1 — закрывает нормативную инверсию.

Изменения:
- CHANGELOG_claude_md.md: +v2.0 entry (полное описание Phase 3-4 — 4
  normative rewrites Pravila v1.14 / PSR_v1 v3.0 / CLAUDE.md v2.0 /
  Tooling v2.0 + effective-state candor)
- CLAUDE.md §6: «Tooling v2.0 (pending)» → «(commit f65a8d7)»
  («(pending)» annotation stale после всех 4 Phase 3 commits)
- PSR_v1 история версий v3.0 entry: «CLAUDE.md/Tooling v2.0 (pending)»
  → commit hashes 5df88a1/f65a8d7
- cspell-words.txt: +«спеке» (Russian locative inflection of «спека»)

Cross-refs audit (plan §4.1.1): проверены v1.13/v2.1/v1.17/v1.93 refs
во всех 4 normative files — все current-state cross-refs корректно
bump'нуты в Phase 3 commits; остаточные старые версии встречаются
только в frozen changelog entries + «Введено в vX» исторических
маркерах + «vX+» forward-compat нотации (не stale).

Phase 3-4 завершён: Pravila v1.14 (9c3057b), PSR_v1 v3.0 (d30cbeb),
CLAUDE.md v2.0 (5df88a1), Tooling v2.0 (f65a8d7), sync (this).

Related: ruflo v3.7.0-alpha.38 integration via spec/plan 2026-05-15
(e55572e/a68a0a0/18c4463/9bd1bae); Phase 1-2-5-6-7 prior session.
2026-05-15 11:22:14 +03:00
Дмитрий f65a8d79ec docs(tooling): §0 35 → 55 + new §4.10 Orchestration layer (ruflo) — v2.0 (ruflo big-bang Day 3)
Ruflo big-bang Phase 3 Task 3.4 (финальный). Major bump v1.17 → v2.0:
ruflo формализован как четвёртая off-phase подкатегория «orchestration».

Изменения:
- Header v1.17 → v2.0, date 15.05.2026
- §0 summary table: +row «ruflo orchestration layer» (+20 plugins);
  count «35 формализованных позиций» + 20 ruflo plugins = 55 total
- §0 «Назначение» line — sync stale «33» (pre-v1.17 oversight) → 35+20=55
- §4.9 +note «Категории off-phase tools (v2.0)» — 4 подкатегории
  (UI-пул / infrastructure / debug-runtime / orchestration)
- §4.10 (new) «Orchestration layer (ruflo) — entry-point иерархии»:
  npm package + repo + namespace, 20 plugins / ~210 MCP tools / 60+
  agents, архитектурная роль (entry-point level −1), категория,
  установка (commit 55c49c9), cost-budget, runtime state candor
  (daemon/swarm/memory НЕ активны — opt-in MCP, paper-level), IPFS
  gateway risk, Связано-links
- §11/§12 — sync stale «33» → «35» (pre-existing v1.17 oversight)
- История версий: +v2.0 table row + footer note

Effective-state candor: §4.10 явно фиксирует — scaffold installed,
MCP server в .mcp.json, но daemon/swarm/memory не initialized; ruflo
доступен как opt-in MCP (7-й из 7), не enforcing Queen-led overlord.

Phase 3 завершён (4/4 normative rewrites): Pravila v1.14 (9c3057b),
PSR_v1 v3.0 (d30cbeb), CLAUDE.md v2.0 (5df88a1), Tooling v2.0 (this).
Pending Phase 4: cross-refs sync + CHANGELOG_claude_md.md +v2.0 entry.

Related: ruflo v3.7.0-alpha.38 integration via spec/plan 2026-05-15
(e55572e/a68a0a0/18c4463/9bd1bae).
2026-05-15 11:18:09 +03:00
Дмитрий 5df88a1310 docs(claude_md): §1 +уровень −1 ruflo + §3.5 orchestration + §5 п.10 sub-policy note + §6 ruflo phase — v2.0 (ruflo big-bang Day 3)
Ruflo big-bang Phase 3 Task 3.3. Major bump v1.93 → v2.0: 8-level → 9-level
priority chain, ruflo Queen-led routing на уровне −1 (entry-point).

Изменения:
- Header v1.93 → v2.0 (architectural inversion description + полный
  legacy tail v1.93→v1.86 preserved)
- §0 cross-refs: Pravila v1.13 → v1.14 (commit 9c3057b), PSR_v1 v2.1 →
  v3.0 (commit d30cbeb), Tooling v1.17 → v2.0 (§4.10 Orchestration layer)
- §1 priority chain: +уровень −1 «ruflo Queen-led routing (entry-point)»
  над уровнем 0; уровни 0-6 byte-identical (становятся execution layer);
  +trailing explanation
- §3 title «35 инструментов» → «35 + ruflo orchestration layer»;
  +§3.5 (new) «Off-phase orchestration: ruflo»; §3.5 «Заметки к
  settings.json» renumber → §3.6
- §5 п.10: +inline sub-policy note (claude-md-management остаётся
  preferred channel через ruflo routing; ruflo agents могут править
  напрямую при явном routing-decision)
- §6: +2026-05-15 ruflo big-bang integration paragraph над «Post-MVP»
- §9: +v2.0 entry

Effective-state candor: §3.5/§6/header/§9 явно фиксируют paper-level
architectural commitment — ruflo daemon/swarm/memory НЕ initialized
2026-05-15; ruflo доступен как opt-in MCP tool, не enforcing Queen-led
overlord. Технические компенсаторы (gitleaks/RLS/dev DB) сохраняются.

Прямой Edit per plan §1.4 — user-authorized exception к §5 п.10
(claude-md-management обязательный канал не применён по решению
заказчика для нормативной инверсии).

Pending Phase 3 sibling: Tooling v2.0. Phase 4: cross-refs sync +
CHANGELOG_claude_md.md +v2.0 entry + «(pending)» annotations cleanup.

Related: ruflo v3.7.0-alpha.38 integration via spec/plan 2026-05-15
(e55572e/a68a0a0/18c4463/9bd1bae); Phase 3 commits 9c3057b/d30cbeb.
2026-05-15 11:07:51 +03:00
Дмитрий d30cbeba10 docs(psr_v1): R0 stack-gate → sub-policy paired-stack delegation pattern — v3.0 (ruflo big-bang Day 3)
Ruflo big-bang Phase 3 Task 3.2. Major bump: R0 «единый stack и обязательный
gate» → «Sub-policy: paired-stack delegation pattern (под ruflo Queen-led
routing)».

Изменения:
- R0 title rewrite (sub-policy framing)
- R0.1 «Уровень и головенство» — добавлен top row «−1. ruflo Queen-led
  routing (entry-point, v3.0+)»; PSR_v1 row «— это и есть stack» → «sub-policy
  ruflo routing»
- R0.2 «Обязательный gate» — первый параграф переписан: ruflo первой,
  stack-gate как sub-policy через routing-decision. Subsequent R0.2 sub-points
  + ASCII gate diagram сохранены (semantic tension — diagram pre-v3.0
  visualization, кандидат на follow-up polish)
- R0.6 «Hard-стоп даже в Auto mode» — добавлен пункт 11 (sequential
  continuation после v2.0 R15 removal; spec литерально писал «п.12», но
  фактический list содержит 1-10, sequential = 11): «ruflo Queen routes
  task как autonomous swarm, но human absent для review — pause до review»
- Принцип-аксиома (line 10) переформулирован под ruflo: stack — головной
  при решении задач, маршрутизированных в paired-stack sub-policy через
  ruflo (entry-point −1)
- Header version v2.1 → **v3.0**, date 13.05.2026 day +1 → 15.05.2026
  afternoon, summary paragraph + narrative tail
- История версий: v3.0 entry на верху (sequential continuation note)
- Cross-refs: CLAUDE.md v1.88+ → v2.0+, Pravila v1.11+ → v1.14+ (commit
  9c3057b), Tooling v1.16+ → v2.0+ (§4.10 Orchestration layer)

R0.3 «Структура stack'а», R0.4.A SoT cross-ref на Pravila §12.3, R0.4.B
live-команды table, R0.5, R1-R14 правила — preserved untouched.

Pending Phase 3 sibling: CLAUDE.md v2.0, Tooling v2.0. Phase 4: cross-refs
sync + CHANGELOG_claude_md.md +v2.0 entry.

Related: ruflo v3.7.0-alpha.38 integration via spec/plan 2026-05-15
(e55572e/18c4463/9bd1bae/9c3057b).
2026-05-15 10:59:02 +03:00
Дмитрий 9c3057b473 docs(pravila): §12 hard rule → sub-policy + §5 ПДн execution-layer note — v1.14 (ruflo big-bang Day 3)
Ruflo big-bang Phase 3 Task 3.1 — переводит §12 Superpowers из «hard rule» в
«sub-policy под ruflo Queen-led routing» (routing preference для interactive
turns; не absolute block). §12.2 карта 14 типов задач + §12.3 exclusions SoT
+ §12.4 details сохранены содержательно — меняется только framing.

§5 ПДн получает execution-layer note: gitleaks pre-commit фильтр —
технический compensator, не зависит от regulatory hierarchy, продолжает
работать выше ruflo routing.

§0 priority chain + §0 «Особый статус §12» note синхронизированы с
sub-policy framing. PSR_v1 cross-refs в §11.5/§13.2/§13.9/§13.10 bumped
v2.0/v2.1 → v3.0+ (R0 → sub-policy). CLAUDE.md → v2.0+, Tooling → v2.0+
в changelog block.

Pending Phase 3 (sibling normative rewrites): PSR_v1 v3.0, CLAUDE.md v2.0,
Tooling v2.0. Phase 4: cross-refs sync + CHANGELOG_claude_md.md +v2.0 entry.

Related: spec docs/superpowers/specs/2026-05-15-ruflo-integration-design.md
(e55572e+a68a0a0), plan docs/superpowers/plans/2026-05-15-ruflo-big-bang-integration.md
(18c4463+9bd1bae), Phase 2 install 55c49c9, map fork 796d814.
2026-05-15 10:50:09 +03:00
Дмитрий 9bd1baedef fix(plan): broken relative links in §4.1.2 CHANGELOG entry template
Phase 6 lychee regression выявил 2 broken-link в
docs/superpowers/plans/2026-05-15-ruflo-big-bang-integration.md:680 —
template для CHANGELOG_claude_md.md entry имел relative paths
`(specs/...)` и `(plans/...)` которые резолвились в
docs/superpowers/plans/specs/... и docs/superpowers/plans/plans/...
(double-prefix, файлы не существуют).

Fix: changed к correct relative form:
- specs/... → ../specs/... (parent dir)
- plans/... → 2026-05-15-ruflo-big-bang-integration.md (same dir, bare
  filename)

Per Pravila §4.7 п.4 + memory quirk 76: relative paths from plans/specs
require explicit `../<sibling>/` или bare filename для same-dir.

Lychee post-fix: 487 OK / 0 errors (was 485 OK / 2 errors pre-fix).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 10:29:28 +03:00
Дмитрий 796d814e62 feat(graph): automation-graph-ruflo.html — fork iter3 с ruflo overlay
User's primary asked deliverable: «отдельный проект карты с его внедрением»
(2026-05-15 session). Fork of docs/automation-graph.html iter3 (commit
8a22cc4) with ruflo big-bang overlay (TO-BE structure).

Map additions vs source:
- GROUPS: +ruflo (orange #ff8800, top-of-hierarchy semantic)
- NODES: +10 ruflo (Queen в верхнем-левом углу за пределами radial-sector
  + 9 swarm-roles в circle ~180px вокруг Queen: Architect, Coder,
  Security, RLS-reviewer, QA, Tester, Reviewer, Memory-keeper, Daemon-worker)
- EDGES: +18
  * 9 Queen → swarm (подчиняет)
  * 4 Queen → group-centroids pravila/claude_md/psr_v1/tooling
    (перенял sub-policy)
  * 5 swarm → legacy execution-layer (делегирует TDD/RLS/HNSW/PM2)
- CONFLICTS: +3 BLACK «возник на практике»
  * Queen ↔ pravila: alpha-tool overrides hard-rule §12
  * daemon ↔ mem_state: static .md vs HNSW dual-system синхронизация
  * Queen ↔ mcp_pw: IPFS gateway flaky (Pinata + Cloudflare failed
    2026-05-15) → plugin discovery offline риск
- HTML: comment header с source/spec/plan refs; title updated
- footer cat-legend: +🌊 ruflo Queen + swarm item (filter-key
  group:ruflo)

NOT in scope этого commit'а:
- subPolicy:true overlay для 73 legacy nodes (polish item, plan §5.4)
- Visual smoke в Edge — manual task для пользователя
- Phase 3-4 normative file rewrites — DEFERRED to separate session

cspell vocab additions для Phase 3-5 normative rewrites (lowercase per
user-dict case rule): sub-policy, queen-led, hive-mind, orchestrator,
autopilot, poincaré.

Refs:
- spec docs/superpowers/specs/2026-05-15-ruflo-integration-design.md
  (commit e55572e — base + a68a0a0 — Phase 1 sync)
- plan docs/superpowers/plans/2026-05-15-ruflo-big-bang-integration.md
  (commit 18c4463 — base + a68a0a0 — Phase 1 sync)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 10:27:47 +03:00
Дмитрий 55c49c9889 feat(ruflo): install scaffold + MCP entry + cost-budget — Phase 2 install
ruflo v3.7.0-alpha.38 installed via npx ruflo init --full --no-global
--with-embeddings --force. 86 files / 9 directories scaffolded.

Successful artifacts (kept, gitignored):
- .claude-flow/ — V3 runtime (config.yaml, data/, logs/, sessions/)
- .claude/agents/ — +23 ruflo agent subdirs (analysis, architecture,
  browser, consensus, core, custom, data, development, devops,
  documentation, flow-nexus, github, goal, optimization, payments, sona,
  sparc, specialized, sublinear, swarm, templates, testing, v3)
  — auto-regenerable via ruflo init, не tracking
- .claude/commands/ — 10 ruflo slash-commands (gitignored)
- .claude/helpers/ — ruflo CLI helpers (gitignored)

Restored from backups (ruflo init --force overwrote, intentional plan §3
will rewrite manually):
- CLAUDE.md (76068 bytes / 280 lines — original restored from
  CLAUDE.md.pre-ruflo.bak; Phase 3 Task 3.3 will manually add ruflo
  level −1 chapter)
- .claude/settings.json (2681 bytes — original restored from
  .claude.pre-ruflo.bak/settings.json; Phase 2 Task 2.10 will manually
  add memory reindex PostToolUse hook)
- .mcp.json (3718 bytes — git checkout HEAD; now extended manually with
  ruflo entry below)

Custom subagents preserved untouched:
- .claude/agents/pest-parallel-debugger.md
- .claude/agents/rls-reviewer.md
- .claude/skills/ untouched

This commit changes (tracked):
- .gitignore — +21 ruflo paths (.claude-flow/, CLAUDE.local.md, agent
  subdirs, commands/, helpers/, backups, transient logs)
- .mcp.json — +ruflo entry (7th MCP server: playwright + github +
  laravel-boost + semgrep + sentry + redis + ruflo). stdio mode,
  Task 1.6 verified no port-conflict.

Not committed (gitignored):
- .env.local — RUFLO_DAEMON_MAX_USD_PER_DAY=10 (spec §7 cost-budget)
- CLAUDE.md.pre-ruflo.bak — backup, kept on disk
- .claude.pre-ruflo.bak/ — backup, kept on disk

Out of scope Phase 2 (deferred decision):
- Task 2.5 settings.json enabledPlugins.ruflo-* — plan based on
  misunderstanding (ruflo is not a Claude Code plugin, it's MCP server +
  CLI; «plugins» внутри ruflo управляются `ruflo plugins install`, не
  через ~/.claude/settings.json). Skipped.
- Task 2.8 PM2 daemon-as-service — deferred to Phase 6 (post-regression
  verification что ruflo MCP не ломает существующие tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 10:22:13 +03:00
Дмитрий a68a0a0ccb chore(spec): Phase 1 pre-flight findings — sync spec + plan + cspell vocab
Phase 1 Task 0 verifications executed on 2026-05-15 against live Windows
Server 2022 + native PowerShell elevation + Node.js stack:

- Task 1.1 npm view: ruflo v3.7.0-alpha.38 (not alpha.33 as spec assumed),
  MIT, repository.url = ruvnet/claude-flow.git (rename Jan-2026 incomplete
  in npm metadata; plugin namespace also remains @claude-flow/*).
- Task 1.2 CLI: 33+ subcommands available — init, mcp, plugins, daemon,
  doctor, hive-mind (Queen-led consensus), autopilot, claims, cleanup, etc.
- Task 1.3 plugins list: 20 plugins in IPFS-registry (not 32 as spec
  estimated). Registry CID QmeXmAdbWVvT84GfDXPD2Vg1HWhiTW2VdZfRLhkS96KkX2
  fetched via IPFS — gateway.pinata.cloud + cloudflare-ipfs.com FAILED,
  only ipfs.io worked. 6 core + 1 command + 13 integration. 11 CRM-relevant;
  9 nichе (medical/legal/financial/quantum). User decision gate confirmed
  «full big-bang — all 20» despite material delta from spec.
- Task 1.4 disk: 67 GB free (>> 5 GB requirement).
- Task 1.5 elevation: TRUE — pm2-service-install без эскалации заказчику.
- Task 1.5.2 PM2 not yet installed.
- Task 1.6 MCP: stdio mode confirmed (INFO [claude-flow-mcp] Starting
  in stdio mode) — no port conflict with existing MCP entries. Resolves
  spec §12 Q5.

Material changes vs original spec/plan:
- 32 → 20 plugins (1.6× smaller actual scope)
- 100+ → 60+ agents (per npm description)
- Plugin namespace ruflo-* → @claude-flow/* (legacy)
- Added §10.3 risks #11 (IPFS gateway), #12 (alpha version inconsistency
  3.0.0-alpha.1..8), #13 (namespace mismatch documentation cost)
- §3 rewritten with concrete 20-plugin table and CLI subcommand list
- §12 Q1/Q4/Q5/Q7 marked RESOLVED with concrete answers
- §12 +Q11 (IPFS) +Q12 (version inconsistency)

cspell vocab additions: ruvector, ipfs, xenova, onnxruntime (lowercase
per user-dict case rule, see commit e55572e for prior precedent).

Plan synced: alpha.33 → alpha.38 (replace_all), 32 plugins references
patched at 8 specific locations. Tooling §0 row description updated:
+20 plugins (35 → 55 formalized), not +32 (35 → 67).

Awaiting user OK for Phase 2 (destructive scaffolding starts at Task 2.1
CLAUDE.md backup + Task 2.2 npx ruflo init).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:50:53 +03:00
Дмитрий 0ae92e2937 test(admin): G2 review fix — coverage for load() fetchError path
Code-review fix для commit e0bbf4d (G2 AdminSupplierPricesView errors):

I-2 (load() coverage gap): Добавлен 1 test «load() sets fetchError when
axios.get rejects». Раньше load() error handling (try/catch + fetchError
ref + v-alert warning) реализован но без test coverage. Reviewer flagged
как low-risk gap. Now covered.

Tests 8/8. Регрессий 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:31:36 +03:00
Дмитрий 18c4463ddd docs(plan): ruflo big-bang integration — 7-phase implementation plan
Spec reference: docs/superpowers/specs/2026-05-15-ruflo-integration-design.md
(commit e55572e). Plan implements full architectural big-bang per user
choice (Approach A + «чистый верх» + map fork + cost-benefit table +
compressed in-session execution path).

Structure:
- Phase 1 Pre-flight Task 0 (~15min): 6 tasks verifying ruflo CLI/plugins
  list/MCP smoke/disk/elevation → spec §12 Q1-Q10 resolution + commit.
- Phase 2 Install (~20min): backup CLAUDE.md, ruflo init, .gitignore,
  .mcp.json, settings.json plugins, .env.local cost-budget, optional
  PM2 daemon-as-service. Memory reindex hook (Task 2.10).
- Phase 3 Rewrite 4 normative files (~25min, parallel subagents):
  Pravila v1.13→v1.14, PSR_v1 v2.1→v3.0, CLAUDE.md v1.93→v2.0,
  Tooling v1.17→v2.0. 4 atomic commits. cspell vocab prep Task 3.0.
- Phase 4 Cross-refs sync (~10min): CHANGELOG +v2.0 entry, version drift
  check across 4 normative files.
- Phase 5 Map fork (~20min): docs/automation-graph-ruflo.html fork iter3
  with ruflo group + Queen + 9 swarm-roles + 4 Queen→centroid edges +
  3 new BLACK conflicts + footer cat-legend. 73 legacy nodes get
  subPolicy flag + opacity 0.7.
- Phase 6 Regression (~15min): Pest 742+, Vitest 736+, lychee, gitleaks,
  vue-tsc, phpstan, ruflo doctor, pm2 status.
- Phase 7 Closure (~5min): CHANGELOG regression numbers, push origin
  main, memory update.

Self-review: spec coverage 13/14 sections mapped to tasks; §6 memory
bridge hook gap closed by adding Task 2.10; cspell prep gap closed by
Task 3.0; 4 [TBD reference] placeholders documented as runtime
substitutions with explicit owner/timing.

Total compressed: ~110min in single session.

Decision gates: Phase 1 pre-flight critical fails → STOP plan + escalate.
Phase 2 step 2.2.4 CLAUDE.md modification by init → CRITICAL revert from
backup + escalate. Phase 6 regression fail → Day 7 closure NOT executed
until 0 failed.

Awaiting user choice: Subagent-Driven (recommended for parallel Phase 3)
vs Inline Execution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:30:01 +03:00
Дмитрий e0bbf4d134 fix(admin): G2 — error/success handling in AdminSupplierPricesView save
axios.patch теперь в try/catch с extractErrorMessage() helper. Per-row
ошибки — reactive errorMessages: Record<number, string> отображаются как
v-icon mdi-alert-circle с v-tooltip рядом с кнопкой «Сохранить».
Success — v-snackbar (3s timeout, color=success, bottom-right) с именем
поставщика.

Retry на той же строке очищает предыдущий error перед новым axios.patch.

load() тоже обёрнут — fetchError ref + v-alert warning сверху таблицы.

+3 Vitest specs (save error / save success / retry clears error).
Регрессий 0.

Closes audit ID G2 from docs/superpowers/specs/2026-05-15-portal-audit-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:26:50 +03:00
Дмитрий 0047aa4ccd test(admin): G1 review fixes — mock cleanup + successToastOpen coverage
Code-review fixes для commit 72a0064 (G1 AdminPricingTiersView errors):

I-1 (mock leak risk): Добавлен afterEach(() => vi.clearAllMocks()) в
новый describe block. Раньше axios.isAxiosError.mockReturnValue(true)
оставался активным после run'а нового describe. Сейчас нет других
тестов после G1 describe в файле — но future-proof против перестановки
test order.

I-2 (coverage gap): Оба success теста (submit + confirmDelete) теперь
assert vm.successToastOpen === true. Раньше тест мог пройти, если
кто-то забыл successToastOpen.value = true в impl — message set, но
snackbar не открыт. Now covered.

Tests 9/9. Регрессий 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:20:51 +03:00
Дмитрий e55572e22c docs(spec): ruflo big-bang integration design v0.1 + cspell vocab
Full architectural inversion: ruflo Queen-led routing as top entry-point,
existing Pravila §12 / CLAUDE.md §5 п.10 / Pravila §5 ПДн / PSR_v1 R0
become sub-policies. 14 sections: goals, architecture (8→9 levels),
scope (32 plugins), big-bang sequencing (~1.5h compressed in-session),
map fork, memory bridge HNSW, cost-budget controls (\$10/day cap),
Windows daemon, safety walls, cost-benefit deliverable (9 benefits +
8 costs + 10 risks), verification, open questions (10 Q's pre-flight
Task 0), termination, self-review.

Brainstorming via superpowers:brainstorming, economy 0%. User chose
Approach A (Full big-bang) + «чистый верх» architectural model +
map fork (vs side-by-side / new layout) + cost-benefit table deliverable
+ compressed in-session execution path (vs 7-day staged).

cspell-words.txt additions (lowercase per user-dict case rule):
ruflo, ruvnet, hnsw, sona, ruvllm, многоагентный, форк, форка, bak.

Awaiting user review of written spec before invoking writing-plans.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:19:24 +03:00
Дмитрий 72a00641fa fix(admin): G1 — error/success handling in AdminPricingTiersView submit/delete
axios.post/delete теперь обёрнуты в try/catch с extractErrorMessage()
хелпером из api/client.ts (same pattern as AdminSystemView.vue:32-45).
errorMessage отображается в v-alert (closable, type=error, tonal),
successMessage — в v-snackbar (color=success, 4s timeout).

На failed submit диалог остаётся открытым чтобы пользователь мог
исправить и повторить (UX-pattern). saving=false гарантированно
сбрасывается в finally.

+4 Vitest specs (submit error / submit success / delete error / delete success).
Регрессий 0.

Closes audit ID G1 from docs/superpowers/specs/2026-05-15-portal-audit-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:12:19 +03:00
Дмитрий e8d5025656 fix(projects): C5 — replace window.alert() with v-snackbar in BulkActionsBar
window.alert блокирует UI thread, не accessible (a11y), breaks браузерный
automation (Playwright/Selenium). Заменено на v-snackbar (timeout 6s,
color warning, location bottom-right, кнопка «Закрыть»). Текст идентичен:
«Применено: N. Пропущено: M (конфликт с уже доставленными лидами).»

+2 Vitest specs (snackbar opens / snackbar НЕ opens at skipped=0).
window.confirm для pause/resume/archive намеренно оставлен — это
deliberate blocking прерывание для деструктивных операций (UX-pattern).

Closes audit ID C5 from docs/superpowers/specs/2026-05-15-portal-audit-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:02:47 +03:00
Дмитрий 061532c53a refactor(kanban): C4 review fixes — array-revert test coverage + JSDoc
Code-review fixes для commit 9068005 (C4 KanbanView DnD persist):

I-2 (test coverage gap): Revert test «onColumnChange reverts...» теперь
seed'ит deal в dealsByStatus['hot'] до вызова onColumnChange (имитируя
vuedraggable mutation pre-event). После failed transition — assert
карточка удалена из hot + восстановлена в new. Раньше array-revert
branch в KanbanView.vue:80-87 (splice + push) имел 0 test coverage —
findIndex возвращал -1, splice silent. Теперь coverage 100%.

I-3 (stale JSDoc): File-header comment в KanbanView.vue lines 7-16
обновлён — описывает actual behavior после Task 2 (optimistic + API call
+ revert). Раньше явно врал «не входит в этот коммит: PATCH /api/deals/
{id}» когда POST /api/deals/transition уже реализован.

Регрессий 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:58:11 +03:00
Дмитрий 9068005566 feat(kanban): C4 — persist DnD status changes via POST /api/deals/transition
Drag-drop между колонками теперь сохраняется в БД через существующий
DealBulkActionController@transition endpoint (single-element массив).
Optimistic UI update (statusSlug меняется сразу) + revert-on-fail с
toast «Не удалось переместить — восстановлен исходный статус».

Без auth.user.tenant_id (dev/demo без login) — local-only mode, API не
зовётся (graceful degradation).

+3 Vitest specs в KanbanView.spec.ts (success / revert / no-auth skip).
Pest covered by existing DealTransitionTest. Регрессий 0.

Closes audit ID C4 from docs/superpowers/specs/2026-05-15-portal-audit-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:51:21 +03:00
Дмитрий c09c52ea76 refactor(deals): C2 review fixes — watcher-driven draft + named toggles
Code-review fixes для commit 4e77947 (C2 FilterChip popovers):

I-4 (latent interaction bug): Удалена двойная open-path в FilterChip
activator. v-menu сам управляет projectMenuOpen/managerMenuOpen через
activatorProps. Draft-state копируется при menu open → true через
watch(menuOpen, ...). Раньше:
- Activator click: menuOpen=true
- @click on FilterChip: onRedesignFilterClick → menuOpen=true (duplicate)
- Re-click для close: activator toggles false → onRedesignFilterClick
  forces true back → menu не закрывается.

I-2 (inline toggle extract): Multi-line ternary @click заменён на
named methods toggleProjectDraft(proj) / toggleManagerDraft(name).
Консистентно с existing clearProjectDraft / clearManagerDraft. Также
unit-testable независимо от template.

onRedesignFilterClick остаётся для Status chip read-only behavior (P2
backlog Sprint 5). defineExpose обновлён: убран onRedesignFilterClick,
добавлены toggleProjectDraft/toggleManagerDraft/clearProjectDraft/
clearManagerDraft (для будущих spec'ов).

Vitest 3/3 C2-specs обновлены на прямой trigger projectMenuOpen=true
+ $nextTick (watcher seeds draft). Регрессий 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:43:49 +03:00
Дмитрий 4e779471fd feat(deals): C2 — wire FilterChip popovers (Проект/Менеджер) with v-menu
Заменён dead-stub onRedesignFilterClick (console.log only) на работающие
v-menu popover'ы. Project и Manager chip'ы открывают v-card с v-list checkbox-
multi-select, бинд на projectMenuDraft/managerMenuDraft → Применить → перенос
в существующие filterProjects/filterManagers refs. Status chip остаётся
read-only (P2 backlog Sprint 5).

+3 Vitest specs в DealsViewRedesign.spec.ts (toggle menu / apply selection /
empty state). Регрессий 0.

Closes audit ID C2 from docs/superpowers/specs/2026-05-15-portal-audit-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:31:42 +03:00
Дмитрий 3d32ed52bd docs(plan): Sprint 1 — 5 P0 UI fixes (C2/C4/C5/G1/G2) implementation plan
Atomic TDD plan, 5 tasks, each task: file:line targets + red test scaffold +
green implementation code + verification commands + commit message draft.

- C2 DealsView FilterChip popovers (Проект/Менеджер) — v-menu wrapping
- C4 KanbanView DnD persist через POST /api/deals/transition
- C5 BulkActionsBar window.alert() → v-snackbar
- G1 AdminPricingTiersView submit/delete try/catch + v-alert + snackbar
- G2 AdminSupplierPricesView save per-row error + tooltip + snackbar

0 schema changes. Reuses existing endpoints + extractErrorMessage helper.
Sprint Acceptance: Pest 749+/Vitest 92+/0 regressions/5 atomic commits.

+1 cspell entry: unpushed (dev-process vocab).

Source spec: docs/superpowers/specs/2026-05-15-portal-audit-design.md (e978b33).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:22:37 +03:00
Дмитрий e978b33cdd docs(spec): portal-wide audit & proposals — 70 items, 6-sprint schedule
Comprehensive audit of Лидерра portal from user's perspective:
- 4 parallel Explore-agents (auth/app-user/admin/shared) → 100+ raw findings
- Router (26 SPA) vs AppSidebar (7 items) vs AdminLayout (5/7 admin routes) coverage
- ТЗ v8.5 §6 CSV-import gap analysis: schema partially ready, code 0% implemented
- Cross-ref with Открытые_вопросы v1.83 (87/71 /11 ⏸)
- Playwright MCP browser smoke (login flow + console + network)

Output: 70 atomic IDs in 11 categories (A-K), groupable to ~30-35 epics,
scheduled across 6 sprints by priority P0 → P1 → P2 → P3 → 🆕 NEW → 🧹 CLEAN.

Sprint 1 (P0, ~2 days): C2 FilterChip popovers + C4 Kanban DnD persist +
  C5 BulkActionsBar window.alert→snackbar + G1+G2 admin error handling.
Sprint 4 (🆕 H1-H9, ~5 days): CSV-import module per ТЗ §6 (исторические
  лиды + проекты from crm.bp-gr.ru → tenant в Лидерре, idempotent через
  webhook_dedup_keys advisory-lock, transaction type historical_import).

Approval: Дмитрий 2026-05-15 night «всё в работу, спринты по приоритету»
через superpowers:brainstorming flow. Next: writing-plans for Sprint 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:11:49 +03:00
Дмитрий aa3976380d fix(plan-6): replace broken absolute memory-link with plain-text reference (pre-push lychee unblock) 2026-05-15 08:10:33 +03:00
Дмитрий 8a22cc45c5 docs(graph): iter3 closure — spec + plan + smoke evidence + cspell terms
iter3 «Automation Graph — interactive highlighting» закрыт.
8 implementation commits ef88435..f0d3d49 (6 feat + 2 fix).
Smoke 12/12 PASS via Playwright (raw JSON + 2 screenshots).
markdownlint/cspell/lychee — clean. Final cross-commit review: APPROVED.

+spec/plan: docs/superpowers/{specs,plans}/2026-05-15-graph-*.md
+smoke evidence: docs/smoke-2026-05-15-graph-highlighting-scenario{2,9}.png
+cspell: NEIGHBOURS / neighbour / BFS / DFS (iter3 vocabulary)

iter4 backlog (non-blocking): I-1 falsy-coercion line 1531, dead var
highlightedNode, SECTION 6 comment update, optional rAF-throttle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 07:04:57 +03:00
Дмитрий f0d3d492a7 feat(graph): btn-clear + search input integration with highlight state 2026-05-15 06:30:55 +03:00
Дмитрий a37d32d3f7 fix(graph): use setSelectedNode API instead of direct state mutation (code review) 2026-05-15 06:28:03 +03:00
Дмитрий b9917a90d4 feat(graph): network click → selectedNode + toggle on repeat 2026-05-15 06:24:06 +03:00
Дмитрий d2fa107d11 feat(graph): legend click delegation — toggle filter + apply highlight 2026-05-15 06:20:22 +03:00
Дмитрий ac2d173089 feat(graph): SECTION 8 — state + indices + opacity computations (infra) 2026-05-15 06:12:43 +03:00
Дмитрий 0bd55b2dbd feat(graph): add data-filter-key to 12 .cat-item elements 2026-05-15 06:07:16 +03:00
Дмитрий 0b6694e802 fix(graph): add intent comment between split .cat-item rules (code review) 2026-05-15 06:04:27 +03:00
Дмитрий ef88435348 feat(graph): CSS rules for interactive legend (.cat-item hover/active states) 2026-05-15 06:00:14 +03:00
Дмитрий 4bdb996c6c feat(ui): subject-level regions autocomplete in NewProjectDialog + PDD (Plan 6 Task 5)
- projectsStore: Project.regions?: number[] interface field
- NewProjectDialog: replace interim placeholder с v-autocomplete (89
  subjects + federal district subtitle); form drops region_mask/region_mode
  (backend dual-writes)
- ProjectDetailsDrawer: replace maskToCodes/encode-watch с direct
  form.regions binding; same v-autocomplete component
- Vitest: +2 NewProjectDialog tests (count=89, POST payload includes regions[]);
  refactor 3 existing PDD region tests на regions[] model

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 05:54:05 +03:00
Дмитрий 830e7fc3d7 feat(supplier): outbound adapter direct-copy regions[] (Plan 6 Task 4)
SyncSupplierProjectsJob::adaptProjectsForAllocator no longer converts
8-bit region_mask via bitmaskToList. Instead direct-copies projects.regions[]
(89-code subject array) into supplier_projects.current_regions / DTO.

region_mask still dual-written for PhonePrefixService backward-compat (Plan 6.5
cleanup will switch readers and drop dual-write).

+2 Pest tests verifying direct copy + empty-array semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 05:43:49 +03:00
Дмитрий c1ecefafc0 feat(projects): backend support for subject-level regions array (Plan 6 Task 3)
- Project model: +regions in fillable + cast via PostgresIntArray
  (custom Eloquent cast for PG INT[] — Laravel stock 'array' uses JSON
  which Postgres rejects on native INT[] columns)
- StoreProjectRequest / UpdateProjectRequest: drop region_mask/mode rules,
  add regions array validation (1..89 each, present/sometimes)
- ProjectService::create: dual-write — regions источник истины + legacy
  region_mask=255 + region_mode='include' для PhonePrefixService/LeadRouter
  compatibility (Plan 6.5 cleanup will remove dual-write)
- +5 Pest tests covering create/update/dual-write/validation rejection
- Drive-by: SchemaDeltaTest indexes pin 117 → 118 (Plan 6 v8.20 carryover
  from Task 1; should ideally have landed in Task 1 commit c487641)
- phpstan-baseline: +3 entries for Project::$regions until next ide-helper
  regen; existing Pest actingAs counts bumped 9→12 / 6→8 for new tests

Verified: Pest --parallel 747/744/3sk/0/0 (5 new tests pass +
SchemaDeltaTest now green), phpstan 0 errors, pint clean, gitleaks 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 05:39:43 +03:00
Дмитрий f467409baf chore(regions): expand REGIONS constant 31 → 89 + add federal district mapping
89 субъектов РФ по конституционному порядку (ст. 65, ред. 2022).
Adds federalDistrict field for UI group-by + FEDERAL_DISTRICT_NAMES map.
Sentinel code:0 "Вся РФ" сохранён для UI hint; в БД = regions=[].
Plan 6 (см. docs/superpowers/specs/2026-05-14-plan-6-regions-subject-level-design.md).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 05:01:12 +03:00
Дмитрий c4876410ea db(schema): v8.20 — add projects.regions INT[] for subject-level filtering
Adds INT[] column + GIN index to support 89-code regions (Plan 6).
region_mask/region_mode kept for backward-compat (DEPRECATED, removal in Plan 6.5).
Empty array semantically equivalent to legacy region_mask=255 (all of Russia).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 04:52:19 +03:00
Дмитрий e8cc1f1105 docs(plan-6): regions subject-level — design spec + implementation plan
PDD regions feature (commits 4f60add..f982046) shipped с 32-bit маской на
31 субъект, incompatible со schema's 8-битным region_mask CHECK 0..255 →
500 on POST. Interim A (commit b1c3efa) откатил UI; этот эпик возвращает
поле в правильной модели.

Approach 2 — dual-write transition:
- Add projects.regions INT[] (89 codes, GIN-indexed)
- region_mask/region_mode legacy preserved для PhonePrefixService/LeadRouter
  compatibility (Plan 6.5 cleanup)
- Direct copy в supplier_projects.current_regions без bitmask conversion
- UI: <v-autocomplete> с 89 subjects + federal district subtitle

Spec — 14 sections (scope, approach, schema, REGIONS, validation, UI,
outbound, data flow, migration, testing, error, assumptions, OOS, refs).

Plan — 6 tasks (12 new tests, 3 PDD tests refactored):
- Task 0: orientation + baseline
- Task 1: schema delta v8.20 (1 commit)
- Task 2: REGIONS const 31→89 (1 commit) — 89 entries inline по
  конституционному порядку
- Task 3: backend (Store/Update/Service/Model + 5 Pest)
- Task 4: outbound adapter (SyncSupplierProjectsJob + 2 Pest)
- Task 5: frontend (Project type + NewProjectDialog + PDD + 5 Vitest)
- Task 6: regression sweep + close

Key insight (from brainstorming): SupplierProjectDto::regions уже
типизировано array<int, int|string> — supplier API contract supports
89 codes натively, не нужно изменений downstream.

5 ASSUMPTIONS marked в spec §12 (regions order, Москва/МО separate,
existing projects→[], dual-write window, UI subtitle vs subheader) —
confirmed via brainstorming session.

Drive-by: cspell-words.txt +1 entry «федокруг» (term проекта,
используется в spec и других docs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 19:31:23 +03:00
Дмитрий 700814c389 chore(env): switch QUEUE_CONNECTION to redis (CLAUDE.md §2 compliance)
Job dispatch fell с SQLSTATE[42P01] "Undefined table: jobs" when
QUEUE_CONNECTION=database, потому что db/schema.sql не содержит таблиц
jobs/job_batches (CLAUDE.md §6 claim "3 default Laravel-миграции удалены"
не имел эквивалента для jobs в нашей schema; verified via
Schema::hasTable('jobs') = false).

Switch to redis — соответствует prod spec CLAUDE.md §2 "Кэш/очереди = Redis 7"
и существующему Memurai service (Redis 7-compat) per memory quirk #35
(PONG verified Task 4).

Verified end-to-end:
- php artisan config:clear
- config('queue.default') = redis
- Queue::connection('redis') instanceof Illuminate\Queue\RedisQueue
- SyncSupplierProjectsJob::dispatch(1) → Redis::llen('queues:default')
  delta=1 (before=0, after=1, cleanup successful)
- Pest --parallel 742/739/3sk/0
- Vitest 758/3sk/0

Local app/.env (gitignored) уже на redis с прошлой сессии; этот commit
синхронизирует normative .env.example для new env setups.

Note: db/schema.sql миграция jobs/job_batches таблиц отложена (redis driver
= no DB queue tables needed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 19:28:54 +03:00
Дмитрий b1c3efa1e1 fix(projects): #909 СОЗДАТЬ кнопка — apiClient + interim A regions
Root causes:
1. Default axios без withXSRFToken не отправлял CSRF header → 419 silent
   fail (catch ловил только 422).
2. PDD regions UI (commits 4f60add..f982046) использовал 32-bit маску,
   несовместимую с schema's 8-битным CHECK chk_projects_region_mask_range
   → 500 silent fail.

Changes (NewProjectDialog.vue):
- Replace default axios import с apiClient + ensureCsrfCookie +
  extractErrorMessage из api/client.ts (same pattern как NewDealDialog).
- await ensureCsrfCookie() перед mutating; apiClient.post/patch.
- Remove regions <v-autocomplete> + selectedRegions ref + inverted
  region_mode watcher (interim A — proper 89-codes реализация в Plan 6).
- Add general error banner для non-422 ошибок (419/401/500/network).
- form.region_mask=255 + region_mode='include' (schema default = вся РФ).

Changes (EditProjectDialog.spec.ts):
- Switch mock с default axios на apiClient (cascading from above).

Verified: Pest 742/739/3sk/0, Vitest 758/3sk/0, vue-tsc 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 19:28:33 +03:00
Дмитрий f9820460fa feat(pdd): regions multi-select autocomplete + bitmask binding
Реализует Out-of-plan «Region multi-select autocomplete» из parent PDD spec.
Spec: 4f60add. Plan: 159ed3e.

Component (ProjectDetailsDrawer.vue):
- import REGIONS из constants/regions
- selectedRegions: Ref<number[]> + selectableRegions (filter code !== 0
  для исключения «Вся РФ» sentinel — fixes latent NewProjectDialog bug)
- maskToCodes(mask): reverse-decompose bits 1..31
- reseedFromProject: +selectedRegions.value = maskToCodes(form.region_mask)
- watch(selectedRegions): forward-encode mask + mode (include при empty, exclude иначе)
- Template: v-autocomplete multi+chips+clearable между Лимитом и Днями

Tests (ProjectDetailsDrawer.spec.ts): 17 passed (14 prior + 3 new):
- renders region chips when project has non-zero region_mask
- selecting regions encodes mask + sets mode=exclude on save
- clearing all regions resets mask=0 + mode=include on save

NB: config.global.plugins = [createVuetify()] добавлен в spec.ts — v-autocomplete
требует Vuetify defaults provide context. Все 17 PDD tests + 8/1sk ProjectsView
integration green (0 regressions).

Backend без изменений (region_mask + region_mode payload уже в Task 5 onSave).
2026-05-14 17:51:56 +03:00
Дмитрий 159ed3eb86 docs(plan): PDD regions field — 1 TDD task + verify sweep
Implementation plan для regions multi-select autocomplete в PDD
(spec: 4f60add docs/superpowers/specs/2026-05-14-pdd-regions-field-design.md).

Task 1 (atomic TDD):
- Step 1: read current state
- Step 2: append 3 failing tests (chips render / select-encodes / clear-resets)
- Step 3: verify 3 RED
- Step 4: implement (REGIONS import + selectedRegions ref + maskToCodes
  helper + watch + reseed line + template autocomplete)
- Step 5: 17 PDD tests pass
- Step 6: vue-tsc + ESLint 0 errors
- Step 7: ProjectsView integration tests still 8/1sk
- Step 8: atomic commit

Task 2 (verify-only):
- Full vitest suite 92f/758+3sk
- Vite build sanity
- Visual smoke 8-step handoff to user

Spec coverage: 100% (verified inline in plan §Self-Review).
Out-of-plan: composable extraction / NewProjectDialog backport TODO / bigint /
mobile — all explicitly deferred.

NB env quirk: Write/Edit may silently fail on cyrillic-path — workaround
через ASCII-Temp + PowerShell Copy-Item задокументирован в plan header.
2026-05-14 17:44:36 +03:00
Дмитрий 4f60add187 docs(spec): PDD regions field — autocomplete + bitmask binding
In-place port региона multi-select autocomplete в ProjectDetailsDrawer.
Закрывает Out-of-plan «Region multi-select autocomplete» из parent spec
(2026-05-14-project-details-drawer-design.md §7).

Подход A (утверждён 2026-05-14):
- v-autocomplete :items="REGIONS.filter(r => r.code !== 0)" (без sentinel)
- reverse-decompose existing region_mask в codes[] при reseedFromProject
- watch selectedRegions → encode mask + mode (include когда пусто, exclude иначе)
- 3 новых vitest case: render chips / select-encodes / clear-resets

Backend без изменений (region_mask + region_mode payload уже в Task 5 onSave).
Backport reverse-decompose в NewProjectDialog (TODO line 172) — out of scope.

cspell-words.txt +1 (иммутабельны).
2026-05-14 17:40:43 +03:00
Дмитрий 0d7f505185 docs(spec): PDD §7 Out-of-scope expanded with reviewer-flagged polish-debt
After SDD execution (9d88955..c5814ec) reviewers flagged 11 non-blocking
issues across Tasks 2/5/6/7/8/9. User decision 2026-05-14: ship as-is, defer
all polish to Plan 6+. Spec §7 расширен 3 кластерами:
- Token drift (4 hardcoded hex × #0f6e56/#f59e0b/#dc2626/480px → CSS vars)
- UX gaps (network-error snackbar / drawer a11y role+aria / Lucide icons)
- Test hardening (testid symmetry / clearAllMocks / .length / comment fix)
+ Cross-cutting silent-error pattern + Sentry breadcrumbs (Б-1 pending).

Полный feature функционально работает (Vitest 92f/755+3sk/0, vue-tsc 0,
ESLint 0, Vite build 2.50s). Polish-debt не блокирует ship.
2026-05-14 17:22:58 +03:00
Дмитрий 2ad35cac72 chore(graph): T11 — Style Guide v2 re-rewrite (clarity for non-tech reader)
Дмитрий обнаружил regression в visual smoke iter2: T2-T5 rewrite сохранил тех-жаргон. Пример MCP: semgrep when «Фаза 3 pre-production: при ревью кода (sk_coderev), при CI перед релизом» — непонятен нон-tech reader'у («Фаза 3»/«pre-production»/«sk_coderev»/«CI»).

Применены 4 новых правила Style Guide v2:
- Фазы 0-3 раскрыты («нулевая/первая/вторая/третья фаза» + контекст)
- Аббревиатуры в скобках с переводом (CI/BYPASSRLS/SAST/XSS/SQLi/PR/RLS/MCP/READ-ONLY/ПДн)
- Узловые ID запрещены — «(sk_coderev)» → «(скил code-review)», «(mcp_redis)» → «(MCP-сервер redis)»
- Английские тех-термины переведены (production→боевая среда, pre-production→перед запуском, race condition→гонка, off-phase debug-runtime→вне основных фаз — для отладки во время работы, subdir-only→из подкаталога)

Затронуты узлы: claude_md/sk_coderev/mcp_boost/mcp_semgrep/mcp_sentry/mcp_redis + label конфликтного ребра ag_pest↔mcp_redis + EDGE_DETAILS для psr_v1→upm/mcp_21st + claude_md→mcp_sentry/mcp_redis.

NODE_DETAILS=73 (intact), EDGES=74 (intact), EDGE_DETAILS=71 (intact), conflict edges=8 (intact). JS syntax OK 89440 chars.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 17:15:39 +03:00
Дмитрий c5814ecc9c test(projects): integration tests for drawer × bulk-bar mutual exclusion 2026-05-14 17:14:12 +03:00
Дмитрий bfdab40d88 feat(projects): integrate ProjectDetailsDrawer + swap bulk-bar condition >=2
Task 8 of project-details-drawer plan (2026-05-14):
- ProjectsView.vue: import ProjectDetailsDrawer + computed
  - singleSelectedProject computed (Project|null when selectedIds.size === 1)
  - onDrawerClose/onDrawerSaved handlers (clearSelection / fetch)
- Template: BulkActionsBar condition > 0 → >= 2 (mutual exclusion with drawer)
- Template: mount <ProjectDetailsDrawer> with :project / @close / @saved bindings
- Template: .has-drawer class on .projects-view root when single selected
- Style: .projects-view padding-right 480px transition for push effect
- Test: ProjectsView.spec.ts pre-existing 'shows BulkActionsBar' case updated
  to assert >=2 contract (selects 2 projects); 14 PDD tests + 3 view tests
  + 1 skip + toolbar tests all green

Vitest: 3 files / 20 passed / 1 skipped / 0 failed
2026-05-14 15:02:33 +03:00
Дмитрий ae6a370b06 feat(pdd): Delete button with confirm + archive + close 2026-05-14 14:54:55 +03:00
Дмитрий 8aca5b1ba9 feat(pdd): Pause/Resume button with toggleActive + dynamic label 2026-05-14 14:48:24 +03:00
Дмитрий 86b18fc396 feat(pdd): Save action — PATCH /api/projects/{id} + 422 errors 2026-05-14 14:41:28 +03:00
Дмитрий f47ace40f4 feat(pdd): reseed form on project.id change 2026-05-14 14:35:54 +03:00
Дмитрий 66d0d48adf feat(pdd): emit close on X/Cancel/ESC 2026-05-14 14:28:49 +03:00
Дмитрий fa01951d27 feat(pdd): render project name/limit/days form fields 2026-05-14 14:21:07 +03:00
Дмитрий 7d77187eb3 test(pdd): scaffold ProjectDetailsDrawer + null-project no-open test 2026-05-14 14:13:52 +03:00
Дмитрий fb235e9d8d docs(plan): ProjectDetailsDrawer — 10 atomic tasks (TDD-strict)
Implementation plan для side-panel редактирования single-selected проекта
на /projects (spec: 9d88955 docs/superpowers/specs/2026-05-14-project-details-drawer-design.md).

10 tasks:
 1. Scaffold + null-project no-open test
 2. Render name/limit/days fields
 3. Close emits (X / Cancel / ESC × 2 negative case)
 4. Form reseed on project.id change
 5. Save — PATCH /api/projects/{id} + 422 errors
 6. Pause/Resume + label switch
 7. Delete with confirm
 8. ProjectsView wire (condition >0 → >=2, drawer mount, computed, .has-drawer CSS)
 9. ProjectsView integration tests (5 cases: 0/1/2 selected + close + missing id)
10. Full regression + visual smoke (9 manual checks)

Каждая task: failing test → verify FAIL → impl → verify PASS → commit (TDD-strict).
9 кодовых commits + Task 10 verification only.

Coverage: 16 spec cases (11 unit + 5 integration) реализуются полностью.
Out of plan: confirm dialog при dirty Cancel / optimistic update / mobile / region
autocomplete (region_mask payload-only в Save, UI порт в отдельный sweep).

cspell-words.txt +1 (pdd) — namespacing prefix data-testid'ов компонента.

NB env quirk: Write/Edit tools silently fail on cyrillic repo path —
workaround через ASCII-Temp + PowerShell Copy-Item задокументирован в шапке плана.
2026-05-14 13:38:04 +03:00
Дмитрий 9d889558d3 docs(spec): ProjectDetailsDrawer push-mode design + mockup
Design spec + интерактивный HTML mockup для side-panel редактирования
проекта при выборе одного проекта на /projects.

Поведение:
- selectedIds.size === 1 → drawer справа (480px, push-mode, grid сдвигается)
- selectedIds.size >= 2 → BulkActionsBar внизу (условие в ProjectsView.vue:78
  меняется > 0 → >= 2)
- 0 selected → ни drawer, ни bulk-bar

Footer drawer:
- Слева (destructive): Приостановить (toggle-active) + Удалить (soft-archive)
- Справа (form actions): Отмена (close+clearSelection) + Сохранить
  (PATCH /api/projects/{id})

Backend без изменений — используются существующие endpoints PATCH/DELETE/
toggle-active. Pinia store useProjectsStore уже имеет update/toggleActive/
archive методы.

Прецеденты: DealDetailDrawer.vue (overlay-вариант); push-mode здесь — custom
aside + CSS transform/padding-right, без Vuetify teleport.

Mockup: 3 состояния через JS-toggle (0/1/2+ selected), Forest palette
(Teal #0F6E56, ivory #F6F3EC, noir #012019). Phone masked под 152-FZ ПДн.

cspell-words.txt +1 (юнит) — для упоминания юнит-тестов в spec §6.

Open questions: 0 (все 5 UX-решений утверждены заказчиком 2026-05-14).
2026-05-14 13:33:27 +03:00
Дмитрий 3cd4ac7c59 feat(graph): 3-color conflicts render + sort 🔴🟢 + footer cat-legend
.conflict-item теперь использует динамический bg из CONFLICT_TYPES[type].bg, эмодзи-префикс + цветной name из CONFLICT_TYPES[type].color. Сортировка через CONFLICT_TYPES[type].rank (RED=1, BLACK=2, GREEN=3) — 🔴 не закрыт правилом →  возник на практике → 🟢 закрыт правилом. Footer cat-legend заменил 1 «— конфликт» бэйдж на 3 цветных. Iter2 spec §4.3 — last code task.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:16:56 +03:00
Дмитрий 8b0da60114 feat(graph): edge legend render + click handler for 7-field edge profile
#legend-panel разделён на 2 containers: #legend-node-content (existing) + #legend-edge-content (new, hidden default). На click по ребру открывается edge layout с 7 полями (источник/получатель/тип связи/когда/что передаёт/обязательность/регламент). showLegend переименована в showNodeLegend; новая showEdgeLegend. Click handler dispatches node vs edge. Iter2 spec §5.4, §5.5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:14:20 +03:00
Дмитрий 32396d97de feat(graph): EDGE_DETAILS data structure (5-field profile for all edges)
Новый объект EDGE_DETAILS — для каждого ребра 5 полей (type/when/transfers/mandatory/rule). Источник и получатель derived from from/to при рендере в T8. Покрытие 100% — все рёбра имеют запись. Тип связи: enum из 9 (содержит/подчиняет/координирует/читает/запускает/документирует/триггерит/альтернатива/конфликт). Iter2 spec §5.1, §5.2, §5.3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:11:12 +03:00
Дмитрий cec1a0c979 fix(graph): T6 — remove orphan hookify_plugin.conflicts[1] (economy-mode item)
T6 spec review нашёл orphan item: hookify_plugin conflicts array имел 2 items (PreToolUse:CLAUDE.md-warn + economy-mode хук), но в spec §4.2 классифицирован только первый (8 рёбер, hookify↔hk_economy не среди них). Item 2 без canvas edge counterpart. Remove restores invariant: 16 NODE_DETAILS conflicts items = 8 edges × 2 sides.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:04:01 +03:00
Дмитрий 93ca58896f feat(graph): 3-color conflicts data (CONFLICT_TYPES + 2 new edges + reclassify 6)
CONFLICT_TYPES enum (RED/BLACK/GREEN с color/bg/emoji/label/rank), CONFLICT() helper расширен опциональным `type` (default RED). 6 существующих рёбер реклассифицированы: 2 🔴 (sk_rls↔ag_rls, hookify↔hk_pre_claude), 4 🟢 (psr_v1↔claude_md, upm↔fd, 21st↔fd, economy↔superpowers). 2 новых  ребра: mcp_pw↔sk_parallel (browser-in-use, квирк #2), ag_pest↔mcp_redis (Redis race в Pest --parallel, квирк 72). NODE_DETAILS conflicts items получили field `type` для всех 12 existing + 4 new items. Iter2 spec §4.1, §4.2, §4.4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:59:25 +03:00
Дмитрий f6cd79ccb9 chore(graph): rewrite group D (lefthook + memory, 25 nodes) — plain language
Переписаны nd() блоки для 10 lefthook jobs (lh_gitleaks/lh_mdlint/lh_cspell/lh_stylelint/lh_pint/lh_larastan/lh_squawk/lh_eslint/lh_gitleaks2/lh_lychee) и 15 memory-файлов. Уточнено что «pre-commit stage» = «перед каждым коммитом», «stage_fixed:true» = «авто-сохранить исправленное». Iter2 spec §3 group D. Last text-rewrite task.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:53:35 +03:00
Дмитрий db7f798a64 chore(graph): rewrite group C (agents + MCP, 18 nodes) — plain language
Переписаны nd() блоки для 11 агентов (ag_pest/ag_rls/ag_explore/ag_general/ag_plan/ag_guide/ag_statusline/ag_hookify/ag_pcreator/ag_pvalid/ag_skreview) и 7 MCP (mcp_pw/mcp_gh/mcp_boost/mcp_semgrep/mcp_sentry/mcp_redis/mcp_21st). Иностранные аббревиатуры расшифрованы (SAST/CVE/SQLi/XSS/ПДн). Iter2 spec §3 group C.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:47:10 +03:00
Дмитрий 718a6e6ff3 chore(graph): rewrite group B (skills + hooks, 21 nodes) — plain language
Переписаны nd() блоки для 14 Superpowers-скилов (sk_brainstorm/sk_tdd/sk_debug/sk_wplans/sk_eplans/sk_verify/sk_parallel/sk_worktree/sk_pr/sk_subagent/sk_wskills/sk_spreview/sk_coderev/sk_elements), 2 проектных (sk_rls/sk_qitem), 5 хуков (hk_pre_claude/hk_post_md/hk_post_schema/hk_session/hk_economy). Жаргон-блэклист убран, параграф-ссылки сохранены. Iter2 spec §3 group B.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:42:32 +03:00
Дмитрий 797a17978d fix(graph): T2 polish — psr_v1.limits terminology + superpowers.when full triggers
T2 code-quality review: (1) psr_v1.limits — нормализован framing 3 rules (R14.5/R6.0/R6.1) под единый «обязательное правило» pattern. (2) superpowers.when — восстановлены 11 trigger keywords (brainstorm/TDD/debug/verify/writing-plans/parallel-work/work-tree/finishing-PR/subagent/writing-skills + творческие) — Дмитрий должен видеть конкретные skill-имена.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:36:51 +03:00
Дмитрий 2db5bd8709 chore(graph): rewrite group A (rules + plugins, 9 nodes) — plain language
Переписаны nd() блоки для pravila/claude_md/psr_v1/tooling/superpowers/fd_plugin/upm/claude_md_mgmt/hookify_plugin. Жаргон-блэклист (hard rule, matcher, pipeline, override, peerDep и др. — 11 терминов) убран; параграф-ссылки сохранены как примечания в скобках. Iter2 spec §3 (Style Guide + group A).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:29:21 +03:00
Дмитрий bcdcca01a5 fix(graph): T1 hardening — localStorage try/catch + rAF throttle on redraw
После T1 code-quality review: 2 Important issues из spec §9 mitigation list. (1) try/catch обернул read/write localStorage — в Edge InPrivate / quota-exceeded не падает, fallback на default 300. (2) network.redraw() rAF-throttled через redrawScheduled flag — устраняет potential jank при fast drag на медленном hardware (mousemove может fire'ить >60Hz).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:22:52 +03:00
Дмитрий 97da018724 feat(graph): resize handle 300-900px + localStorage
Drag-handle 6px на левой границе #legend-panel (cursor:col-resize, hover-bg #0d4a5a), JS-обработчики mousedown/mousemove/mouseup, clamp [300, 900]px, сохранение ширины в localStorage ключ liderra-map-legend-width, restoration on DOMContentLoaded. После каждого resize вызывается network.redraw() для пересчёта vis.js canvas. Iter2 spec §2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:17:08 +03:00
Дмитрий abaeebbde6 docs(plan): automation-graph iter2 — 10 atomic tasks (7 parallel-safe + 3 sequential)
Tasks 1-7 (parallel-safe через dispatching-parallel-agents): T1 resize handle CSS+JS+localStorage, T2-T5 text rewrite groups A/B/C/D (9+21+18+25=73 nodes по Style Guide), T6 CONFLICT_TYPES enum + 2 new  edges + reclassify 6, T7 EDGE_DETAILS data (74 entries). Tasks 8-10 (sequential): T8 edge legend render + click handler (depends T7), T9 3-color render + sort + footer (depends T6), T10 visual smoke + push.

Test strategy для single-file HTML без unit-tests: 3-уровневая verification (Level 1 — Node.js syntax check per Edit, Level 2 — lefthook pre-commit gauntlet per commit, Level 3 — manual visual smoke в Edge browser). Pre-push: gitleaks full-history + lychee. Self-review pass: spec coverage 100%, no placeholders, no type drift.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:12:33 +03:00
Дмитрий c18cc93c78 chore(cspell): +2 words for automation-graph iter2 plan
qitem + skreview — фрагменты идентификаторов узлов карты (sk_qitem, ag_skreview); cspell токенизирует по `_` и видит их как unknown words. Упомянуты в plan-файле как ссылки на task'и/инструменты. Iter2 plan reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:12:21 +03:00
Дмитрий f936944237 docs(spec): automation-graph iter2 — resize + simple-language + 3-color conflicts + edge legend
4 improvements after iter1 ride-out: drag-handle resize 300-900px + localStorage; rewrite 73 nodes to plain language with Style Guide; reclassify 8 conflicts as 🔴 not-closed /  practice-observed / 🟢 closed-by-rule; new 7-field edge legend (source/target/relation-type/trigger/transfers/mandatory/regulation).

Parallel execution strategy: Phase 2 dispatches 6 subagents (P1 resize, P2-P5 text rewrite by category, P6 conflict types + EDGE_DETAILS) returning raw JS blocks; Phase 3 main agent applies 12 atomic Edits in sequence → 11 atomic commits total.

Through superpowers:brainstorming 4-clarifying-question cycle (text scope / conflict classification / resize UX / edge legend fields), all options chosen by Дмитрий explicitly. Self-review pass; no placeholders, no contradictions, 0 open questions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:29:40 +03:00
Дмитрий 8e75951edc chore(cspell): +3 words for automation-graph iter2 spec
зарелизен + отрефакторен (русифицированные tech-термины «released» / «refactored»; используются в iter2 spec §0 Context); cdesc (CSS class из docs/automation-graph.html .conflict-item .cdesc, ссылка в iter2 spec §4.3 renderer changes).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:29:28 +03:00
Дмитрий b73ddaaedd docs(a11y): authenticated rescan baseline + findings (21/21 passing)
Final state docs after a11y rescan session:

- docs/audit-baseline-pa11y.md: «Authenticated rescan 2026-05-14» section
  added (14 new URLs, all 21 passing). Old «out of scope для первой
  baseline» section marked SUPERSEDED. Per-pattern fix table with file
  references + ignored selector rationale. axe-core cross-validation
  results documented (only DevIndexBadge dev-only remains).

- docs/superpowers/audits/2026-05-14-a11y-rescan-findings.md (new):
  Full audit findings doc — TL;DR, scope expansion table, per-pattern
  root cause + fix sections (A-H), axe-core cross-validation, метрики
  до/после, verdict 🟢 GREEN.

Regression sweep:
- Pa11y: 21/21 URLs passed
- Vitest: 91 files / 736 passed / 3 skipped / 0 failed
- Pest --parallel: 742/739/3sk/0
- Vite build: ~2s
- gitleaks: 0 leaks / 457 commits / 12.72 MB
- lychee: 345 OK / 0 errors / 457 total
- markdownlint: 0 errors (after auto-fix)
- cspell: 0 issues

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:08:08 +03:00
Дмитрий e39a42cfdf fix(a11y): admin search inputs — add label prop for accessible name (Pattern H)
A11y rescan Pattern H — Vuetify <v-text-field> без `label` prop рендерит
empty `<label id="input-v-NN-label">` (referenced via aria-labelledby).
Pa11y/axe видит unlabelled input на /admin/billing (search «Поиск по
названию или ИНН») и /admin/system (search «Поиск по ключу или описанию»).

Initial naive fix добавил `aria-label="..."` — но ARIA priority говорит
aria-labelledby overrides aria-label, поэтому осталось violation.

Final fix: add `label="Поиск"` prop on VTextField. Vuetify рендерит
floating label с правильным accessible text → axe-core resolves через
aria-labelledby chain successfully. Placeholder сохранён (split: «Поиск»
теперь в label, «по названию или ИНН» / «по ключу или описанию» —
placeholder).

Files:
- AdminBillingView.vue:209-217
- AdminSystemView.vue:130-138

Closes Pa11y «label» violations на 2 admin URLs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:07:48 +03:00
Дмитрий 398f6bcf5a fix(a11y): Vuetify tonal alert/chip + text-warning contrast overrides (Patterns C+D+E)
A11y rescan Patterns C+D+E — Vuetify default theme colours для tonal-variant
.v-alert .v-alert__content (4.18:1) и tonal .v-chip__content (success 4.25:1
/ warning 2.25:1), плюс `.text-warning` utility used в count badges (2.03:1
на ivory) — все ниже WCAG 2.1 AA 4.5:1.

Global CSS overrides in app/resources/css/app.css:

Pattern C — alert tonal content (2 URLs: billing, admin/system):
  .v-alert--variant-tonal .v-alert__content {
      color: #0a0700;   /* near-black, 16:1 on ivory */
  }

Pattern D — chip tonal success/warning content (4 URLs: billing,
admin/{tenants,billing,incidents,system}):
  .v-chip--variant-tonal.bg-success .v-chip__content { color: #1f5e3a }
  .v-chip--variant-tonal.bg-warning .v-chip__content { color: #6a4504 }

Pattern E — .text-warning utility (2 URLs: admin/billing «5», admin/incidents
«1»). Critical specificity fix: Vuetify defines selector as
`.v-theme--liderraForest .text-warning { color: rgb(var(--v-theme-warning))
!important }` (specificity 0,2,0 + !important). Naive `.text-warning
!important` (0,1,0) loses on specificity even with !important. Match Vuetify
selector exactly so override wins on cascade order (loaded after Vuetify CSS):

  .v-theme--liderraForest .text-warning,
  .v-theme--liderraForest.text-warning,
  .text-warning {
      color: #6a4504 !important;
  }

Closes 4+11+2 = 17 color-contrast violations across 5 distinct URLs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:07:35 +03:00
Дмитрий 6387706be6 fix(a11y): .sep dot separator contrast 2.92:1 → 5.33:1 (Pattern B)
A11y rescan Pattern B — scoped CSS `.sep { color: #92907b; }` повторяется
в 8 компонентах (page-stats / page-meta / hero-meta containers с точкой-
разделителем `·`). На ivory page background #f6f3ec даёт contrast
2.92:1, ниже WCAG 2.1 AA 4.5:1 threshold.

Fix: #92907b → #6b6356 — same warm-grey hue, darker tone, gives
5.33:1 contrast. 8 files:

- views/{DealsView,BillingView,KanbanView,ReportsView}.vue
- components/dashboard/DashboardPageHead.vue
- components/deals/DealDetailHero.vue
- components/admin/tenants/TenantsStatsHeader.vue
- components/admin/tenant-detail/TenantDetailHeader.vue

Closes Pa11y «color-contrast» violations на /dashboard /billing /reports
(8 .sep elements total flagged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:07:11 +03:00
Дмитрий 667befde96 fix(a11y): add aria-label to mobile nav-icon button (closes Pattern A)
A11y rescan Pattern A — Vuetify <v-app-bar-nav-icon class="d-md-none">
без accessible name. Pa11y/axe видит button в DOM даже на desktop где
он hidden via CSS — флагает «button-name» violation на 9 AppLayout views
(/dashboard, /deals, /kanban, /projects, /billing, /settings, /reports,
/reminders, /admin/tenants).

Fix: AppTopbar.vue:90-94 — `aria-label="Открыть меню навигации"`.

Closes 9 of 14 authenticated routes' a11y violations (down 14→5 affected
URLs after this commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:06:52 +03:00
Дмитрий 35387e8b17 feat(a11y): extend Pa11y scope to 14 authenticated routes + Vuetify hideElements
pa11y.config.json теперь covers 21 URLs (7 guest + 14 authenticated).

Authenticated URLs использует per-URL actions login flow:
1. navigate to /login
2. fill input[autocomplete="email"] = admin@demo.local (DemoSeeder)
3. fill input[autocomplete="current-password"] = password
4. click button[type="submit"]
5. wait for path /dashboard
6. navigate to target URL + wait path

14 routes added: /dashboard, /deals, /kanban, /projects, /billing, /settings,
/reports, /reminders, /admin/{tenants,billing,incidents,system,pricing-tiers,
supplier-prices}.

hideElements extended:
- select[hidden] — Vuetify VSelect рендерит hidden native <select> для
  form-submission compatibility (не visible UX, screenreader skip).
- input[aria-controls^="menu-v-"] — Vuetify VDataTable items-per-page
  combobox с aria-labelledby chain issue (Vuetify-internal pattern).

timeout 30000 → 60000ms, wait 1500 → 2000ms — accommodate Vue SPA async
hydration после login flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:06:40 +03:00
Дмитрий a650484b11 docs(plan): A11y rescan — live portal authenticated routes
11-task plan для повторного a11y-аудита: extend Pa11y от 7 guest URLs к 21
URLs (включая 14 authenticated через per-URL actions login flow), + axe-core
cross-validation via Playwright MCP, + inline fixes для real prod findings.

Closes Audit #3 Phase 7 «authenticated pages out of scope» clause per
explicit user request «Pa11y был настроен на старые HTML-эскизы, проведи
повторно аудит в этой части, чтобы он проверил реальный портал».

Plan: docs/superpowers/plans/2026-05-14-a11y-rescan-live-portal.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:06:19 +03:00
Дмитрий 54ee37c54e feat(graph): physics off by default + buttons restore radial layout + smooth continuous 2026-05-14 09:23:46 +03:00
Дмитрий d75b3b85d3 feat(graph): radial-sector layout — 6 колец × 4 сектора (workflow/UI/infra/data) 2026-05-14 09:22:45 +03:00
Дмитрий 0da8dbf042 feat(graph): when+limits content for memory files (15) — all 73 nodes done 2026-05-14 09:20:54 +03:00
Дмитрий a19bee28be feat(graph): when+limits content for lefthook jobs (10) 2026-05-14 09:19:38 +03:00
Дмитрий 0634426c30 feat(graph): when+limits content for MCP servers (7) 2026-05-14 09:18:45 +03:00
Дмитрий ee958f884a feat(graph): when+limits content for hooks (5) + agents (11) 2026-05-14 09:17:37 +03:00
Дмитрий 2b38e7be32 feat(graph): when+limits content for skills (14 SP + 2 проектных) 2026-05-14 09:16:02 +03:00
Дмитрий 413803e569 feat(graph): when+limits content for rules + plugins (9 nodes) 2026-05-14 09:14:10 +03:00
Дмитрий 1a7cd90c32 feat(graph): nd() helper supports when+limits fields; showLegend renders them 2026-05-14 09:10:57 +03:00
Дмитрий 40b437ccb7 feat(graph): legend panel — add «Когда используется» and «Ограничения» sections 2026-05-14 09:10:15 +03:00
Дмитрий aa258e1ad0 fix(graph): remove edge labels from canvas, move to hover tooltips 2026-05-14 09:09:42 +03:00
Дмитрий 5c2556b73f fix(graph): canvas rendering artifacts — explicit canvas bg + remove hideEdgesOnDrag 2026-05-14 09:09:01 +03:00
Дмитрий e3974482a9 docs(plan): automation-graph refactor — 10 atomic tasks
Implementation plan для spec 2026-05-14-automation-graph-refactor-design.md.
10 tasks, каждый = 1 коммит, в порядке:
1. canvas rendering fix
2. edge labels → tooltips
3. HTML legend sections (когда + ограничения)
4. nd() helper signature + render
5a-5f. when+limits content для 73 узлов (rules+plugins / skills / hooks+agents / MCP / lefthook / memory)
6. radial-sector positioning (ring + sectorAngle на 73 NODES + pos() helper)
7. physics off + button handlers + smooth continuous
8. final smoke + data integrity check

Self-review: spec coverage , no placeholders , type consistency ,
backward-compat nd() handler в Task 4 (for intermediate state).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 09:05:21 +03:00
Дмитрий b747880ddc docs(spec): automation-graph refactor — 4 fixes (фон / подписи / радиальная иерархия / when+limits)
Дизайн рефакторинга docs/automation-graph.html после визуальной проверки
коммита 7ee78a9:
- canvas background на самом canvas + удаление hideEdgesOnDrag (artifacts)
- удаление labels с edges, переход на title-tooltip + legend section
- radial-sector layout: 6 колец × 4 функциональных сектора, physics off
- 2 новые секции легенды: «Когда используется» + «Ограничения»

cspell: +mgmt (валидный идентификатор узла claude_md_mgmt)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 08:55:32 +03:00
Дмитрий ae20033652 docs(claude.md): v1.92 → v1.93 — sync schema header drift 62→63 (Audit #3 P2)
Tail closure of Audit #3 P2 «schema.sql header drift» finding. Schema
source-of-truth was already updated in commit e746b3c (db/schema.sql:4
header «62 базовые таблицы» → «63 (61 regular + 2 partitioned parents:
deals + supplier_lead_costs)»). This commit syncs three CLAUDE.md
references to match.

Touch points:
- Header version 1.92 → 1.93 + description of session
- §0 «Источник истины» row «Схема БД» — 62 → 63 baseline
- §2 «Стек проекта» БД row — 62 → 63 baseline
- §8 self-review triggers row `db/schema.sql` — 62 → 63 baseline
- §9 history — new v1.93 entry summarising 5-commit sprint
  (8ba9c55..c524227), closure tally (1 P1 + 7 P2 + 4 P3), and regression
  check (Pest 742/739/3sk/0, Vitest 91f/736/3sk/0, gitleaks 0/442,
  lychee 325/0).

Via `/claude-md-management:claude-md-improver` per CLAUDE.md §5 п.10
(only sanctioned channel for direct CLAUDE.md edits).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:47:42 +03:00
Дмитрий c5242271d7 chore(p3): close P3 tooling and structural mini-fixes
Closes Audit #3 P3 batch.

Changes:

1. **knip.config.ts cleanup** — remove 4 stale config hints flagged in
   Audit #3 Phase 1B (`ignore: tests/**` redundant since `project` is
   `resources/js/**`; `ignoreDependencies` for vitest/@vue/test-utils/jsdom
   redundant since knip auto-detects test frameworks). Add `histoire.config.ts`
   + `resources/js/histoire.setup.ts` to entry — closes 2 documented FPs
   (histoire.setup.ts + @histoire/plugin-vue unused-flag). Verified:
   `npx knip` exits 0 clean.

2. **Admin table actions column header label** — change `title: ''` →
   `title: 'Действия'` in:
   - TenantsTable.vue (actions column, /admin/tenants)
   - AdminSupplierPricesView.vue (actions column, /admin/supplier-prices)
   Closes axe-core `empty-table-header` violation seen in Audit #3 Phase 7
   on /admin/tenants. Header is now visible in UI (better UX than sr-only
   sleight-of-hand).

3. **npm overrides for lodash** in `package.json` — pin `pa11y-ci > lodash`
   to ^4.17.21. Verified: `npm ls lodash` resolves to lodash@4.17.23 (latest
   4.x; CVE-2021-23337 + GHSA-f23m patched in <4.17.21, our version is above
   that). npm audit may still surface advisory ranges as informational.

4. **Decision doc for pgFormatter (Q.HARD.002)** — explicit FIX-DEFER with
   3-hypothesis comparison (Strawberry Perl install vs sqlfluff replacement
   vs Docker pg_format vs drop SQL formatting). Decision: drop automated
   SQL formatting until Б-1 closure; squawk (linter) covers correctness.
   Addendum: axe-core .v-overlay-container region landmark — no permanent
   axe-core test setup exists, so no whitelist needed at this point.

Verification:
- knip: 0 issues
- vue-tsc: 0 errors
- ESLint: 0 errors
- Vitest: 91 files / 736 passed / 3 skipped (no regressions)
- Vite build: 2.03s

Plan: docs/superpowers/plans/2026-05-14-audit3-deferred-fixes.md Task 4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:38:51 +03:00
Дмитрий c5c0e76950 test(coverage): close F-COV-01/02/03 — ReminderDialog + AdminLayout + api/admin
Closes Audit #2+#3 P2 carryforward triplet (low-coverage files at risk
of silent regression).

Coverage results (Vitest --coverage --coverage.include per-file):

| File | Stmts before | Stmts now | Δ |
|---|---|---|---|
| ReminderDialog.vue | 0% | 95.38% | +95 pp |
| AdminLayout.vue | 9.09% | 95.45% | +86 pp |
| api/admin.ts | 11.53% | 100% | +88 pp |

Branches/Funcs deltas (subagent reports):
- ReminderDialog: Branch 0→97.56%, Funcs 0→85.71%, Lines 0→96.61%
- AdminLayout: Branch 0→90%, Funcs 0→90%, Lines 9.09→94.73%
- api/admin: Branch 0→100%, Funcs 27.27→100%, Lines 11.53→100%

Approach: TDD via @vue/test-utils + Vuetify global plugin + vi.mock for
store/api. Three parallel subagents (general-purpose), each focused on
single target — no production code changes, only test infrastructure.

Coverage areas:
- ReminderDialog (19 specs): rendering, watch(dialogOpen) populate/reset,
  submit create-mode happy + 3 errors, submit edit-mode happy + 1 error,
  cancel, common validation paths
- AdminLayout (16 specs): brand block, 5 nav items, count badges (142/3),
  breadcrumb per route (5 cases + fallback), userInitials computed (4
  cases incl. fallback), userShortName (4 cases), handleLogout call-order,
  active state, aria-label
- api/admin (18 specs): 11 exported functions × happy-path; 2 encodeURI
  edge cases; 4 ensureCsrfCookie call-order verifications via
  invocationCallOrder; 2 error-propagation tests

Verification (full sweep after merge):
- Vitest: 91 files / 736 passed / 3 skipped / 0 failed (+3 files, +53 specs
  from Audit #3 baseline 88/683/3sk)
- Pest --parallel: 742/739/3sk/0 (identical to baseline, 0 regressions)
- Vite build: 2.03s
- vue-tsc: 0 errors
- ESLint: 0 errors

Plan: docs/superpowers/plans/2026-05-14-audit3-deferred-fixes.md Task 3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:37:26 +03:00
Дмитрий e746b3c9a4 chore(cleanup): dead code removal + DemoSeeder env-conditional + schema header drift
Closes Audit #3 P2 batch (knip dead exports/components, DemoSeeder
hygiene, schema header drift).

- Remove app/resources/js/views/admin/AdminPlaceholderView.vue
  (unreferenced placeholder view — confirmed via repo-wide grep, only
  doc references remain)
- npm uninstall concurrently (no script invoked it; --legacy-peer-deps
  for Histoire 1.0-beta.1 peerDep quirk)
- 12 unused exports → internal types (remove `export` keyword):
  - api/admin.ts: AdminTenantsStats, ApiTenantMetrics,
    ApiAdminBillingSummary, ApiAdminIncidentsSummary
  - api/notifications.ts: NotificationEvent
  - api/reports.ts: ApiReportType, ApiReportFormat, ApiReportParameters,
    ReportCounts, ReportQuota
  - composables/mockBilling.ts: TxType
  - composables/useStatusPill.ts: StatusPillSlug
  All 12 are used INSIDE their own file (response shapes), just not
  exported externally — converting to internal types satisfies knip
  without losing type-checking inside the file.
- DatabaseSeeder::run() — DemoSeeder runs only in local+testing envs
  (`migrate:fresh --seed` in dev now produces demo tenant + admin@demo.local
  + 3 projects + ~14 demo deals; prod environments skip)
- db/schema.sql header line 4: «62 базовые таблицы» → «63 базовые
  таблицы (61 regular + 2 partitioned parents: deals + supplier_lead_costs)»
  Closes schema header drift finding from Phase 3.

Verification:
- vue-tsc --noEmit: 0 errors
- ESLint on touched files: 0 errors
- Pest --parallel: 742/739/3sk/0 failed (identical to baseline, no regressions)
- 2243 assertions / 34.46s

Plan: docs/superpowers/plans/2026-05-14-audit3-deferred-fixes.md Task 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:28:44 +03:00
Дмитрий 0c36b7a28d feat(a11y): migrate Pa11y scope from handoff prototypes to live Vue app
Closes Audit #3 sole P1 (F-A11Y-PA11Y-SCOPE-01).

Pa11y was scanning handoff HTML prototypes from liderra_v8_handoff/concepts/
(3 URLs, ~10 contrast violations), NOT the live Vue app. Audit #2 baseline
"0 errors" was inaccurate — real portal was never covered.

Changes:
- pa11y.config.json: now targets http://localhost:8000/<route> for 7 guest
  pages (login, register, forgot, 2fa, recovery, 403, 500)
- pa11y-handoff.config.json: preserves historical handoff baseline as
  opt-in (`npm run a11y:handoff`)
- package.json: new `a11y:handoff` script; `a11y` repointed to live target
- RecoveryCodesView.vue: scoped CSS override fixes Vuetify warning-tonal
  alert content contrast (2.03:1 → ≥4.5:1, color #0a0700 per Pa11y rec)
- .github/workflows/a11y.yml: new CI job with dev-server lifecycle
  (php artisan serve + curl wait-on + Pa11y + screenshot artifact upload)
- docs/audit-baseline-pa11y.md: first live baseline document with per-URL
  status, ignore selectors rationale, re-run instructions

Local verification:
- npm run a11y: 7/7 URLs passed (0 violations)
- vue-tsc: 0 errors
- ESLint: 0 errors
- Vitest: 88 files / 683 passed / 3 skipped / 0 failed (no regressions)

Plan: docs/superpowers/plans/2026-05-14-audit3-deferred-fixes.md Task 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:25:14 +03:00
Дмитрий 8ba9c55724 docs(plan): Audit #3 deferred fixes sprint plan
25 deferred findings (1 P1 + 11 P2 + 14 P3) → 4 task batches:

1. P1 Pa11y scope migration to live Vue app
2. P2 dead code + dev hygiene (knip findings + DemoSeeder + schema header)
3. P2 coverage debt (ReminderDialog + AdminLayout + api/admin via TDD)
4. P3 tooling + structural mini-fixes

Plan: docs/superpowers/plans/2026-05-14-audit3-deferred-fixes.md
Source audit: docs/superpowers/audits/2026-05-14-portal-full-audit-report.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:24:49 +03:00
Дмитрий f9d2452386 docs(audit): finalize portal full audit #3 — report (2026-05-14) 2026-05-14 07:52:27 +03:00
Дмитрий 301334c288 docs(audit): Phase 14 final regression (audit #3) 2026-05-14 07:46:56 +03:00
Дмитрий abb8a5135e docs(audit): Phase 13 categorization + fix decisions (audit #3)
Final audit rollup: 0 P0 / 1 P1 / 11 P2 / 14 P3 (26 total).

Pa11y P1 decision: FIX-DEFER with concrete migration plan
(6 acceptance criteria + 60-120 min estimate). Decision driven by
3-hypothesis analysis: (1) config-only swap surfaces new live-app
violations (color-contrast on DevIndexBadge, region landmarks),
(2) additive both-kept keeps handoff failures blocking CI,
(3) deferred migration with proper sprint task is cleanest path.
Both decision-matrix triggers from brief apply: risk of new
failures without follow-up plan + new CI infra requirement
(live dev server lifecycle).

Carryforward audit: 9 items still open from Audit #2 (all
P2/P3, no regressions). 11 Audit #2 items verified closed in
this audit (bf84568 aria fix, CTO-19 Lucide, Q.DEFER.001-004,
quirks #62/#72/#80, cron, RUNBOOK.md).

FIX-NOW this session: 0 commits (Pa11y deferred per matrix).
FIX-NOW earlier in audit: 1 commit (823da29 cspell inline).
FIX-DEFER documented: 25.
BLOCKED: 0.

Verdict: GREEN — 0 P0, sole P1 is methodology audit-fidelity gap
(Pa11y declared but not exercised against live code); axe-core
via Playwright in Phase 7 provides actual a11y coverage with 0
real prod issues against DevIndexBadge temp feature.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 07:38:25 +03:00
Дмитрий 4b4705295c docs(audit): Phase 10-12 pre-prod/TODO/untracked findings (audit #3) 2026-05-14 07:34:35 +03:00
Дмитрий 9d27783729 docs: commit untracked plan files + parse-bundle-analyze.mjs (audit #3) 2026-05-14 07:29:47 +03:00
Дмитрий 51664a0aa4 docs(audit): Phase 9 bundle analyzer delta (audit #3) 2026-05-14 07:27:36 +03:00
Дмитрий ad89473331 docs(audit): Phase 8 coverage targeted (audit #3) 2026-05-14 07:24:12 +03:00
Дмитрий 8fa545e113 docs(audit): Phase 7 a11y targeted Pa11y+axe-core (audit #3) 2026-05-14 07:20:49 +03:00
Дмитрий 8ec7a8c116 docs(audit): Phase 6 cross-doc integrity findings (audit #3) 2026-05-14 07:14:59 +03:00
Дмитрий 1f43beacc3 docs(audit): Phase 5 UI smoke 22-view Playwright sweep (audit #3) 2026-05-14 07:12:48 +03:00
Дмитрий 9e2914a72d docs(audit): Phase 4 security findings (audit #3)
CI workflows: 3 (sast/dependency-check/trivy), unchanged from Audit #2.
gitleaks delta (9e175a1..HEAD): 0 leaks / 18 commits.
gitleaks full history: 0 leaks / 426 commits.
gitleaks no-git app/: 1847 matches all in gitignored vendor/ +
phpstan-cache; P2: GITHUB_TOKEN env var captured in gitignored
nette DI container cache (not in git history, mitigations in place);
P3: generic-api-key FPs in phpstan.phar / cache suggest gitleaks.toml.
cspell-words.txt +3: nette, phar, serialises.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 06:29:31 +03:00
Дмитрий 93a3c667e0 docs(audit): Phase 3 schema integrity findings (audit #3)
Query results A-G: root_tables=63 (61r+2p), partitions=12,
indexes=289, RLS=39, functions=5 (correct names), triggers=13
logical/19 total, orphan_FK=0. One P2 finding: schema.sql v8.20
header "62 базовые таблицы" drift → actual 63 (deals +
supplier_lead_costs both partitioned parents). All invariants
RLS/functions/orphan-FK pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 06:23:25 +03:00
Дмитрий af97885266 docs(audit): Phase 2 test suite findings (audit #3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 06:19:50 +03:00
Дмитрий 4a5ecb085a docs(audit): Phase 1D SQL static analysis + Phase 1 итог (audit #3)
squawk v2.51.0 — 0 issues (bin\squawk.exe db/schema.sql, exit 0).
pgFormatter — N/A (perl not in PATH, known Q.HARD.002 carryforward).
Phase 1 combined итог: P0=0 P1=0 P2=4 P3=2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 06:13:04 +03:00
Дмитрий 823da293de docs(audit): Phase 1C docs static analysis findings + cspell words (audit #3)
markdownlint=0, cspell=0 (+3 words: shapkas/SUT/SUT's), lychee=318 OK/0 errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 06:09:29 +03:00
Дмитрий 362af8c981 docs(audit): Phase 1B frontend static analysis findings (audit #3) 2026-05-14 06:06:54 +03:00
Дмитрий 85d79499e9 docs(audit): Phase 0 addendum + Phase 1A backend static analysis (audit #3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 06:02:45 +03:00
Дмитрий 07a483333c docs(audit): Phase 0 pre-flight skeletons (audit #3) 2026-05-14 05:59:55 +03:00
Дмитрий 08605cf640 fix(tests): Bus::fake partial + session mock — close quirk #72
CsvReconcileJobTest used Bus::fake() (all jobs), silencing dispatch_sync of
RefreshSupplierSessionJob when a parallel afterEach wiped supplier:session.
Now: Bus::fake([RouteSupplierLeadJob::class]) + anonymous mock that re-puts
the session in handle(), making race-window recovery deterministic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 05:35:06 +03:00
Дмитрий 9a45346205 fix(tests): RefreshDatabase on LookupsTest + ProjectExtensionsTest — close quirk #62
DatabaseTransactions did not prevent cross-session data accumulation in
liderra_testing; count assertions drifted (1465 managers, 519 projects).
RefreshDatabase runs migrate:fresh once per session (RefreshDatabaseState::migrated)
so stale data is wiped at start of each composer test run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 05:29:34 +03:00
Дмитрий 7ee78a9ad0 feat(docs): interactive automation graph — 73 nodes, 6 conflicts, Solarized dark vis.js
Single-file HTML visualization of Лидерра CRM automation system.
vis.js 9.1.9 force-directed graph: 9 color groups (rules/plugins/skills/hooks/
agents/MCP/lefthook/memory), 6 red dashed conflict edges, click-to-legend panel
with 5 sections (что делает / кому подчиняется / кто / одновременно / конфликты),
search + freeze/unfreeze/reset/clear toolbar. Solarized dark theme.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:05:59 +03:00
Дмитрий 9b21bbc1fd docs(spec): automation graph design spec — vis.js Solarized dark, 72 nodes, 6 conflicts 2026-05-13 16:43:13 +03:00
Дмитрий 7007379b40 docs(plans): add test-quality-preprod sprint plan + fix lychee/cspell
Sprint plan B.1/B.2/B.3/A.1/A.2/A.3. Fixes: broken ../../../memory/
link → plain text; cspell-words.txt +аутит (Russian IT verb).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:41:59 +03:00
Дмитрий bf84568837 fix(a11y): add aria-label to VTooltip on /admin/tenants impersonate btn
Audit #2 Phase 10.2 P2: axe-core 4.10 reported aria-tooltip-name
violation — <div role="tooltip"> had no accessible name. Adding
aria-label to <v-tooltip> passes it through to the rendered overlay.
Verified: axe-core on /admin/tenants — 0 tooltip violations post-fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:38:21 +03:00
Дмитрий b241c79773 docs: add RUNBOOK.md — production deployment runbook
Audit #2 Phase 14 P2 fix. Covers: system requirements, DB setup
(ICU collation + roles + migrations + grants), partition bootstrap,
frontend build, Supervisor queue config, cron scheduler, Nginx,
health checks, rolling update sequence, rollback, dev seed,
common issues. cspell-words.txt +mbstring +pcntl (PHP ext names).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:35:19 +03:00
Дмитрий 9530d17981 fix(schedule): register partitions:create-months as daily cron
Audit #2 Phase 14 P2: partition tables were not auto-created.
Without this entry the scheduler never called partitions:create-months,
causing partition exhaustion on the first day of each new month.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:32:56 +03:00
Дмитрий 219f262655 fix(test): ProjectFactory unique name + test:parallel composer alias
fake()->unique()->words(3,true) fixes quirk #77 deterministic collision
on projects(tenant_id,name) UNIQUE in --parallel runs.
test:parallel alias = pest --parallel --recreate-databases (quirk #62/#73).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:32:00 +03:00
Дмитрий e280edd431 style(frontend): apply prettier --write — fix formatting drift
4 files reformatted (import list expansion, line-length wrapping).
Vitest 88/683+3sk green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:30:51 +03:00
Дмитрий 58986a2d74 test(vitest): add testTimeout: 10000 — fix quirk #80 router.spec.ts coverage timeout
v8 coverage instrumentation adds ~10x overhead to router-guard async tests,
pushing past the 5000ms default. Audit #2 Phase 13 finding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 12:48:40 +03:00
Дмитрий 9e175a1fd6 docs(audit): Phase 10.2 axe-core + Q.DEFER.001+002 closure — audit #2 follow-up
axe-core 4.10 на 16 auth views: P2=1 (aria-tooltip-name VTooltip /admin/tenants),
P3=4 кат. (region sitewide, DevIndexBadge temp, empty-table-header 2 views,
page-has-heading-one 1 view). P0/P1=0.

Q.DEFER.001 (Phase 5 24-view smoke) + Q.DEFER.002 (axe-core 16 auth) оба CLOSED.
blocked.md + report.md обновлены. Verdict 🟡 YELLOW, 0 открытых Q-items.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:52:59 +03:00
Дмитрий ec0dd00a93 docs(audit): Phase 5 full 24-view smoke — Q.DEFER.001 closure (audit #2 follow-up)
Playwright MCP iteration по 24 URL (auth + main + admin + 404).
Login/logout flow verified. CTO-19 Lucide icons confirmed holding.
25 screenshots в audit-screens/2026-05-13/. 0 реальных дефектов.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:41:11 +03:00
Дмитрий 43f9c257bc docs(audit): finalize portal full audit #2 — Phase 7-9 + report (2026-05-13)
Phase 7 — Categorize: severity rollup 37 findings (P0=0 / P1=5 / P2=14 / P3=18).
  vs 12.05 baseline (P0=1 / P1=47 / P2=339 / P3=6) — massive improvement.

Phase 8 — Fix loop SKIPPED per hybrid: 0 P0 + 5 P1 все FIX-DEFER known quirks
  (квирки 62/72 + router coverage timeout), не FIX-NOW eligible. 0 atomic
  fix-commits в этой session.

Phase 9 — Final regression: 0 regressions vs Phase 2 baseline (742/738/1/3 Pest,
  88/683/3 Vitest, 35/63 Histoire, 2.15s Vite). Все baseline metrics preserved.

Report.md filled: TL;DR + Phase summaries + метрики до/после + verdict 🟡 YELLOW
+ commits + 3 new quirks (78 branch contention, 79 CWD double-cd, 80 vitest
coverage v8 timeout).

Q-items: Q.DEFER.001 (Phase 5 full smoke) + Q.DEFER.002 (Phase 10 axe auth) deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:13:44 +03:00
Дмитрий 845477603a docs(audit): Phase 10+11+12+13+14 findings batch (audit #2)
Phase 10 — Pa11y 4 guest URLs:  all clean.
Phase 11 — TODO sweep: 19 matches (stable vs 12.05).
Phase 12 — Bundle: critical-path ~189 kB gzip, +25 kB drift vs 12.05.
Phase 13 — Coverage: 78.30/75.78/70.12/80.47. P1 router.spec.ts timeouts под coverage.
Phase 14 — Pre-prod 🟡: P2 Sentry prod SDK missing, partitions cron not registered, runbook отсутствует.

cspell-words.txt: +«редиректится».

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:11:40 +03:00
Дмитрий 31f804581d docs(audit): Phase 6 cross-doc integrity findings (audit #2)
7 normative docs version match (factual vs memory):
- CLAUDE.md v1.92 , Pravila v1.13 , PSR_v1 v2.1 , Tooling v1.17 
- Реестр v1.83 , schema.sql v8.20 , README_АРХИВ v8.5 

routes/web.php: 26 explicit Route::view + Route::fallback — комплектен. 12.05 finding /projects+/reminders+/admin/* missing — fixed `b9038bc`. /admin top-level index new.

Severity Phase 6: P0=0 / P1=0 / P2=0 / P3=0.  vs 12.05 baseline (5 P2 drift) — параллельная сессия PR #4 sync'нула все версии.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:42:07 +03:00
Дмитрий e81b9c45b4 docs(audit): Phase 5 UI smoke (focused) + Q.DEFER blocked entries (audit #2)
Phase 5 reduced scope (transparent): 17 routes HTTP 200  + CTO-19 Lucide structural verification (vuetify.ts:19 import + prod bundle inclusion). Indirect coverage via Vitest 88/683 + Histoire 35/63 + Vite build (Phase 2).

Not covered этой session: Playwright MCP interactive flows для 24 views.

Q.DEFER entries → blocked.md:
- Q.DEFER.001: Phase 5 full 24-view Playwright smoke deferred.
- Q.DEFER.002: Phase 10 axe-core 16 auth views deferred.

Severity Phase 5: P0=0 / P1=0 / P2=0 / P3=1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:40:42 +03:00
Дмитрий 17d530f669 docs(audit): Phase 4 security findings (audit #2)
- 4.1 CI workflows enum (methodology gap closure per Pravila v1.12 §4.6): 3 active (dependency-check.yml + sast.yml + trivy.yml). Semgrep SAST confirmed deployed: p/php + p/javascript + p/typescript + p/secrets, SARIF upload to GitHub Security tab. Q.INFO.001 12.05 closure verified holding.
- 4.2 Gitleaks full history: 401 commits / 12.11 MB / 0 leaks . vs 12.05 (333/11.14) — +68 commits, still clean.
- 4.3 Composer audit cross-link: 0 advisories.
- 4.4 Production secrets grep: 0 AWS prefix, 0 Stripe prefix в app/.

Severity Phase 4: P0=0 / P1=0 / P2=0 / P3=0 — fully clean.

CI security stack полный: SAST + dependency-check + Trivy = pre-prod readiness baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:37:30 +03:00
Дмитрий 75dc375da3 docs(audit): Phase 3 schema integrity findings (audit #2)
Boost MCP queries к dev liderra:
- Root tables: 61 (vs schema.sql v8.20 header 62; vs CLAUDE.md memory dev-actual 75 stale).
- Partition children: 12 (vs header 12 ; vs memory 102 stale — после migrate:fresh).
- Indexes: 289 (vs header 117 stale; vs memory 289 ).
- RLS policies: 39  exact match.
- User functions: 5  exact by name (audit_block_mutation, audit_chain_hash, calc_lead_score, report_jobs_log_export, set_pd_subject_request_deadline).
- Triggers: 19 (vs header 13 stale; vs memory 19 ).
- DB roles 0 by design (dev).
- Orphan FK: 0 .

Severity Phase 3: P0=0 / P1=0 / P2=2 (schema.sql header drift + CLAUDE.md/memory partition drift after migrate:fresh) / P3=0.

Structural integrity 100%, drift только в documentation accuracy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:35:57 +03:00
Дмитрий 22d8613578 docs(audit): Phase 2 test suite findings (audit #2)
- Pest sequential: 742/736/3/3 (квирк 62 cumulative state — 3 expected fails LookupsTest×2 + ProjectExtensionsTest, numbers ↑ vs 12.05: 1465/12176 — больше накопления).
- Pest --parallel --recreate-databases: 742/738/1/3 — 1 sporadic regression vs 12.05 baseline 739/0/3: CsvReconcileJobTest квирк 72 (Redis supplier:session race в parallel subdir-only).
- Vitest: 88f/683/3  exact match baseline.
- Histoire: 35/63  match.
- Vite build: 2.15s  faster than baseline. P2 bundle drift app-B-3WRbXK.js +21 kB raw.

Severity Phase 2: P0=0 / P1=4 (all FIX-DEFER known quirks) / P2=1 / P3=1.

cspell-words.txt: +«квирков» (валидная gen-plural форма).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:34:01 +03:00
Дмитрий 51440f4e6d docs(audit): Phase 1 static analysis findings (audit #2)
Subagent ×4 parallel dispatch результаты:
- Backend (Pint/Larastan/composer audit):  all 0 errors. P3 composer audit network warn (cached DB).
- Frontend (ESLint/vue-tsc/prettier/knip): ESLint 0, vue-tsc 0. P2 prettier 312 files mismatch (87% — generated .histoire/dist + coverage; ~40 real source). P2 knip lucide-vue-next false-positive (dynamic IconSet pattern).
- Docs (markdownlint/cspell/lychee):  all clean (75 md / 88 cspell / 367 links).
- SQL (squawk/pgFormatter): squawk 0. P3 pgFormatter 6284 lines diff — Q.HARD.002 documented «не трогать».

Severity Phase 1: P0=0 / P1=0 / P2=2 / P3=2. vs 12.05 baseline (P1=44, P2=316) — massive improvement.

Также Phase 0 post-pause update: параллельная сессия завершилась PR #4 merge 66ebb22, нормативка bumped до v1.92/v1.13/v2.1/v1.17, +sentry/redis MCP, +SAST workflow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:25:34 +03:00
CoralMinister 66ebb22043 Merge pull request #4 from CoralMinister/feat/claude-automation-norm-sync
docs(meta): sync нормативки — #34 Sentry MCP + #35 Redis MCP (off-phase debug-runtime)Feat/claude automation norm sync
2026-05-13 10:05:26 +03:00
Дмитрий db167c1beb docs(meta): CLAUDE.md v1.91 → v1.92 — §3 +#34/#35 sentry+redis (off-phase debug-runtime)
Применены 9 edits через /claude-md-management:claude-md-improver per §5 п.10:
- Шапка: v1.91 → v1.92 от 13.05.2026 day +1
- §0 row Pravila: v1.12 → v1.13 (§13.2 +Off-phase MCP debug-runtime)
- §0 row PSR_v1: v2.0 → v2.1 (R10.1 Блок 3 +sentry+redis)
- §0 row Tooling: v1.16 → v1.17; «33 формализованных» → «35»
- §1 priority chain row 2b: «33 инструментов» → «35»
- §3 title: «Карта 33» → «Карта 35»
- §3.3 table: +#34 Sentry MCP + #35 Redis MCP rows после #33
- §3.3 footer: «Total: 33 = 29+3+1» → «35 = 29+5+1»
- §9 история: +v1.92 entry

Категория debug-runtime — отдельная от UI-пула (UPM/21st) и от infrastructure
(claude-md-management). Не trigger'ит R6.0/R6.1 и не входит в R14 pipeline.
READ-ONLY usage обязателен.

Связано: Tooling 763aeae (v1.17), PSR_v1 c1f9719 (v2.1), Pravila 318aed4 (v1.13).
PR #3 (cc5f63b) merge precedent. Branch: feat/claude-automation-norm-sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 09:52:39 +03:00
Дмитрий a0fbe53eea chore(cspell): add «нормативку» (accusative case)
Поддержка для CLAUDE.md v1.92 шапка. «нормативки» (genitive) уже в словаре —
inflection-blind cspell не распознаёт «нормативку» автоматически.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 09:52:38 +03:00
Дмитрий 318aed4f2c docs(rules): §13.2 +Off-phase MCP debug-runtime (sentry+redis) — Pravila v1.12 → v1.13
Применены 3 edits per Task 9 drafts (commit 00eb8ad):
- Шапка: v1.12 → v1.13 от 13.05.2026 day +1; +«Что изменилось в v1.13» section
- §13.2 cross-ref на PSR_v1: v2.0 (15 правил R0–R14) → v2.1 (+R10.1 Блок 3 sentry+redis)
- §13.2 +новый абзац «Off-phase MCP debug-runtime (отдельная категория)» после
  «Инфраструктурные плагины» paragraph: sentry-mcp (#34, pending Б-1) +
  redis-mcp (#35, deprecated, Memurai verified)

Категория отдельная от UI-пула (§13.2 paired-stack + UPM + 21st) и от
infrastructure (claude-md-management). Не trigger'ит R6.0/R6.1 stack-фильтры
и не входит в R14 pipeline UI-генераторов. READ-ONLY usage обязателен.

Связано: Tooling v1.16 → v1.17 (763aeae), PSR_v1 v2.0 → v2.1 (c1f9719),
CLAUDE.md v1.91 → v1.92 (next via claude-md-management).
PR #3 (cc5f63b) merge precedent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 09:48:28 +03:00
Дмитрий c1f9719d67 docs(psr): R10.1 Блок 3 +sentry+redis MCP (debug-runtime category) — v2.0 → v2.1
Применены 3 edits per Task 9 drafts (commit 00eb8ad):
- Шапка: v2.0 → v2.1 от 13.05.2026 day +1; L4 narrative +упоминание debug-runtime MCP
- R10.1 Блок 3 (MCP-серверы): +2 строки sentry + redis с категорией debug-runtime
- История версий: +v2.1 entry перед v2.0

NB по drafts correction: drafts указывали "Блок 1" — actual right block для MCP serverов = Блок 3 (MCP-серверы по `~/.claude.json` / `.mcp.json`).

Категория debug-runtime introduced — отдельная от UI-пула (Pravila §13) и infrastructure
(claude-md-management). READ-ONLY usage, не trigger'ит R6.0/R6.1 фильтры, не входит в R14 pipeline.

Связано: Tooling v1.16 → v1.17 (763aeae), CLAUDE.md v1.91 → v1.92, Pravila v1.12 → v1.13.
PR #3 (cc5f63b) merge precedent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 09:47:04 +03:00
Дмитрий 763aeae0a4 docs(tooling): §0 +#34 Sentry MCP + #35 Redis MCP (off-phase debug-runtime) — v1.16 → v1.17
Применены 5 edits per Task 9 drafts (commit 00eb8ad):
- §0 Сводка row off-phase tools: +3 → +5
- §0 footer: «Итого формализованных позиций» 33 → 35
- §4.8 (новый) — #34 Sentry MCP (@sentry/mcp-server@0.33.0+, official; pending Б-1)
- §4.9 (новый) — #35 Redis MCP (@modelcontextprotocol/server-redis@2025.4.25, deprecated Anthropic source; Memurai PONG verified Task 4)
- §13 история: +v1.16 строка (missing gap) + v1.17 строка
- Footer notes: +v1.16 + v1.17 prepended
- Шапка: v1.16 → v1.17 от 13.05.2026 day +1

Категория debug-runtime — отдельная от UI-пула (UPM/21st) и инфраструктурного (claude-md-management).
Не trigger'ит R6.0/R6.1 фильтры и не входит в R14 pipeline.

Связано: PSR_v1 v2.0 → v2.1, CLAUDE.md v1.91 → v1.92, Pravila v1.12 → v1.13 (separate commits).
PR #3 (cc5f63b) merge precedent.

Verification: markdownlint 0 errors, lychee 5/5 OK 0 broken, gitleaks 10.91 KB no leaks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 09:45:04 +03:00
Дмитрий d7d70ccb4d chore(cspell): add 3 words (wenit, FLUSHDB, LPUSH)
Поддержка для Tooling v1.17 §4.9 Redis MCP entry:
- wenit — npm пакет автор (@wenit/redis-mcp-server, post-MVP migration candidate)
- FLUSHDB, LPUSH — Redis команды (forbidden в READ-ONLY usage)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 09:44:50 +03:00
Дмитрий 2ece232fda Merge branch 'main' of https://github.com/CoralMinister/lidpotok 2026-05-13 09:33:14 +03:00
CoralMinister cc5f63b456 Merge pull request #3 from CoralMinister/feat/claude-automation
Feat/claude automation
2026-05-13 09:26:46 +03:00
Дмитрий c0a5fd1807 feat(agent): extend pest-parallel-debugger с quirk 77 (unique-key collision)
Applied 4 edits per quirk-77 plan Task 3:
- Edit 3.1: добавлен Quirk 77 entry в known-quirks section (between Quirk 73 и NB line)
- Edit 3.2: добавлена Hypothesis 4 quirk 77 в diagnostic pipeline (renumber «other» к H5)
- Edit 3.3: обновлён output format template (+Hypothesis 4 row + extended Conclusion options)
- Edit 3.4: обновлён description frontmatter (+quirk 77 classification (d))

Quirk 77: Pest --parallel deterministic unique-key collision на projects(tenant_id, name)
в ProjectBulkActionsTest::rejects_bulk_when_scope_filter_captures_more_than_500_projects.

Evidence (Task 8 baseline check):
- db/schema.sql:836 UNIQUE (tenant_id, name)
- app/database/factories/ProjectFactory.php:23 fake()->words(3, true)
- app/tests/Pest.php:18 // ->use(RefreshDatabase::class)
- app/tests/Feature/Api/ProjectBulkActionsTest.php:194-206 (501-project bulk)
- 2× --parallel runs failed 738/742; sequential isolation 14/14 
- NOT regression from feat/claude-automation (f454e95 audit-2 zero PHP)

Root cause partial: collision matches birthday paradox (~12.5%), но
deterministic-in-parallel vs sequential suggests worker state sharing
(shared Faker seed via PHP global? Eloquent factory caching?). Full RCA pending.

Mitigation: known parallel-only flake; sequential always passes.
Long-term fix candidates documented в quirk entry.

NB: project-local subagent auto-discovery может требовать session restart.

Verification: markdownlint 0 errors, gitleaks no leaks, +13/-3 lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:50:23 +03:00
Дмитрий 0e3f6b2301 docs(plan): quirk #77 candidate plan — Pest --parallel unique-key collision
Plan: docs/superpowers/plans/2026-05-13-quirk-77-pest-parallel-unique-key-collision-plan.md
279 lines, 3 tasks для documenting Task 8 baseline check finding.

Discovery: ProjectBulkActionsTest::rejects_bulk_when_scope_filter_captures_more_than_500_projects
reproducibly fails 738/742 в --parallel --recreate-databases.
Sequential 14/14 . NOT regression from feat/claude-automation
(verified f454e95 audit-2 commit zero PHP touched).

Evidence captured this session:
- db/schema.sql:836 UNIQUE (tenant_id, name)
- app/database/factories/ProjectFactory.php:23 fake()->words(3, true)
- app/tests/Pest.php:18 // ->use(RefreshDatabase::class) (TX rollback only)
- app/tests/Feature/Api/ProjectBulkActionsTest.php:194-206 (501-project bulk)

Tasks:
1. Memory feedback_environment.md +#77 entry (76→77 quirks)
2. MEMORY.md line 5 summary bump
3. .claude/agents/pest-parallel-debugger.md +Hypothesis 4 + output template
   + description frontmatter

Root cause partial: collision pattern matches birthday paradox (~12.5% per-test
prob with ~100-word Lorem ~1M combos), но deterministic-in-parallel vs sequential
suggests worker state sharing (shared Faker seed via PHP global state? Eloquent
factory caching?). Full RCA pending.

Apply-time recommendation: defer until completion plan Task 9 merged,
apply на separate branch feat/quirk-77-update для atomic-commit hygiene.

Verification: lychee 5/5 OK, markdownlint 0 errors, gitleaks 19.07 KB clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:46:07 +03:00
Дмитрий 00eb8ad235 docs(drafts): pre-prep norm-sync edit blocks для Task 9 (5 files, 9 edits)
Drafts file: docs/superpowers/plans/2026-05-13-claude-automation-norm-sync-drafts.md
364 lines, 5 file targets, 9 distinct Edit blocks с OLD/NEW pairs.

Targets:
- Tooling §0 + §4.8 (sentry) + §4.9 (redis) + §13 changelog v1.16→v1.17
- PSR_v1 R10.1 table + история v2.0→v2.1
- CLAUDE.md §3.3 +#34/#35 + §0 cross-refs + v1.91→v1.92 (через claude-md-management plugin per §5 п.10)
- Pravila §13.2 +Off-phase MCP debug-runtime subsection + v1.12→v1.13
- Memory MEMORY.md + reference_archive.md header refs

Critical correction в drafts: original plan Task 9.3 wording «§3.3 +#34/#35» — error.
Tooling §3.3 = «БД-инструменты», off-phase tools живут в §4.5/§4.6/§4.7.
New sentry+redis → §4.8 + §4.9 (new subsections). Corrected throughout drafts.

Plus bonus finding: new Pest --parallel quirk #77 candidate
(ProjectBulkActionsTest unique key collision on parallel worker shared-DB).
NOT regression from feat/claude-automation (verified). Recommendation:
separate follow-up plan to add quirk #77 to memory + extend
pest-parallel-debugger.

Verification: lychee 3/3 OK 0 errors, markdownlint 0 errors after MD032 fix,
gitleaks 27.35 KB scanned no leaks.

Applied: 0 of 9 edits (drafts only, awaiting Task 1 PR merged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:38:13 +03:00
Дмитрий 7db4075107 docs(plan): completion plan для 9 post-implementation tasks
Plan: docs/superpowers/plans/2026-05-13-claude-automation-completion-plan.md
1047 lines, 9 tasks разделены на 3 фазы:
- Phase A (Tasks 1-2): PR creation + Claude Code session reload
- Phase B (Tasks 3-7): hook smoke + Redis check + skill/subagent invocations + Sentry creds
- Phase C (Tasks 8-9): Pest/Vitest regression + sync нормативки (4 sub-files) + merge + worktree cleanup

Architecture decision: Option A (merge feat/claude-automation first, sync нормативки
on separate branch feat/claude-automation-norm-sync). Clean PR audit trail.

Pre-execution baseline captured. Verification: lychee 7/7 OK 0 errors,
markdownlint 0 errors, gitleaks no leaks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:19:40 +03:00
Дмитрий 4822610df5 fix(agent): escape <cmd>/<output> backticks в pest-parallel-debugger
Markdownlint MD033 (no-inline-html) caught <cmd> and <output> placeholders
on line 63 of constraints section as HTML elements. Wrapped в inline-code
backticks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:54:20 +03:00
Дмитрий a2b5126d19 feat(agent): add pest-parallel-debugger subagent
Project-local subagent в .claude/agents/pest-parallel-debugger.md.
Specialized для верифицированных Pest --parallel квирков 72 + 73
в проекте Лидерра (memory feedback_environment.md lines 385, 389):
- quirk 72 — Redis supplier:session race в subdir-only run
- quirk 73 — cumulative state на long sessions

4-hypothesis diagnostic pipeline (real / quirk 72 / quirk 73 / other).
READ-ONLY (tools: Read, Grep, Bash).

NB: quirks 70-71 в memory — про a11y/Vuetify, не Pest — не входят в agent's scope.
Quirks 74-76 — про npm/Lucide/plans paths, тоже не Pest.

Замена generic systematic-debugging для повторяющихся flake патернов.
NB: project-local subagent auto-discovery может требовать session restart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:53:53 +03:00
Дмитрий 995886f73f feat(agent): add rls-reviewer subagent для migration review
Project-local subagent в .claude/agents/rls-reviewer.md.
Specialized для 5-role архитектуры Лидерры (crm_app_user/admin/
supplier_worker BYPASSRLS/readonly/migrator).

Walks 7-item checklist: tenant_id, ENABLE RLS, 2 policies, 5-role GRANTs,
CHANGELOG, squawk. READ-ONLY (tools: Read, Grep, Glob, Bash).

Замена generic security-review для security-critical RLS работ (39 политик).

NB: project-local subagent auto-discovery может требовать session restart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:53:00 +03:00
Дмитрий 99a242c9ed feat(hook): remind db/CHANGELOG_schema.md on db/schema.sql edits (PostToolUse)
PostToolUse hook на Edit|Write matcher — emits stdout reminder если file path
matches regex `(^|/)db/schema\.sql$` (Windows backslashes normalized к `/`).

Runtime enforcement существующего правила CLAUDE.md §5 п.8:
"Не править db/schema.sql без записи в db/CHANGELOG_schema.md."

Self-review (§8) ловит это поздно (после ≥3 групп правок); hook — сразу,
в transcript stdout vs stderr (visible alongside markdownlint output).

Параллельный entry в hooks.PostToolUse array — Claude Code processes oба
markdownlint (для .md без CLAUDE.md) + schema reminder (для db/schema.sql)
независимо на каждом Edit|Write.

Edge case: Bash-обход (echo ... >> db/schema.sql) не покрывается —
known limitation, документировано в spec §4.6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:52:10 +03:00
Дмитрий c5b0cdfe6f feat(hook): block direct edits of root CLAUDE.md (PreToolUse, Option A warning)
PreToolUse hook на Edit|Write matcher — emits stderr warning если file path
exactly === <project>/CLAUDE.md (path.resolve compare, AND CLAUDE_FILE_PATH +
CLAUDE_PROJECT_DIR both injected by Claude Code at hook firing).

Runtime enforcement существующего правила CLAUDE.md §5 п.10:
"Не править этот CLAUDE.md напрямую — только через плагин claude-md-management."

Option A (warning-only) chosen per Task 1 pre-flight Q5: skill-marker detection
ненадёжно в текущей Claude Code (CLAUDE_SKILL_ACTIVE env var inconclusive в Bash
session — injection-only при hook firing, не verifiable без live test). Warning
visible в transcript stderr; если invoked via /claude-md-management:*, warning
информационный, не блокирует.

Не trigger'ит для:
- app/CLAUDE.md (Boost-managed, не существует на момент implementation)
- node_modules/*/CLAUDE.md (если есть — не root project)

Edge case: Bash-обход (sed -i CLAUDE.md или > CLAUDE.md) не покрывается —
known limitation, документировано в spec §4.5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:51:34 +03:00
Дмитрий e9880a1c1b feat(skill): add /rls-check — 7-item RLS checklist для new tables
Project-local skill в .claude/skills/rls-check/SKILL.md.
Инкапсулирует security-critical check: tenant_id, ENABLE RLS, 2+ policies,
5-role GRANTs (db/02_grants.sql), CHANGELOG, squawk, smoke test.

disable-model-invocation: true — для физического вызова при modify db/schema.sql.
Полезно для security-critical правок (39 RLS политик × 5 ролей).

NB: project-local skill auto-discovery может требовать session restart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:50:51 +03:00
Дмитрий e642cfeb53 feat(skill): add /q-item-add — добавление Q-item в реестр Открытых_вопросов
Project-local skill в .claude/skills/q-item-add/SKILL.md.
Инкапсулирует 6-шаговый workflow: detect section → find next number →
insert entry → update §0 counters → bump versions → sync CLAUDE.md §0.

disable-model-invocation: true — только пользовательская инвокация
(Pravila §2.2: добавление Q-item требует явного запроса заказчика).

NB: project-local skill auto-discovery может требовать session restart
(Task 1 pre-flight outcome: inconclusive direct test, conservative assumption).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:49:15 +03:00
Дмитрий bd4ec48f05 feat(mcp): add redis-mcp server entry
Memurai (Redis 7-совместимый Windows service, localhost:6379).
Pending формализация в Tooling §3.3 #35 — sync нормативки отдельным планом.

Package: @modelcontextprotocol/server-redis@2025.4.25 — DEPRECATED
по npm статусу («Package no longer supported»), но Anthropic source,
простой протокол, рабочий. Post-MVP migration на community alternative
(e.g., @easy-mcps/redis-mcp-server@1.0.8 или @wenit/redis-mcp-server@1.0.3)
когда подтвердим trust.

READ-ONLY use — отладка очередей, кэша, Pest --parallel quirk 72.
Gitleaks scan (manual via absolute path): no leaks found.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:48:20 +03:00
Дмитрий 6f7e7d72fa feat(mcp): add sentry-mcp server entry
Self-hosted Sentry в Yandex Cloud (CLAUDE.md §2). Pending формализация
в Tooling §3.3 #34 — sync нормативки отдельным планом.

Package: @sentry/mcp-server@0.33.0+ (official sentry-bot,
repo getsentry/sentry-mcp, bin sentry-mcp).
Env vars: SENTRY_URL, SENTRY_AUTH_TOKEN — injected via shell, не commit'ятся.

Gitleaks scan (manual via absolute path due to worktree): 800 bytes,
no leaks found. ${SENTRY_*} placeholders confirmed safe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:47:41 +03:00
Дмитрий f454e95a2d docs(audit): Phase 0 pre-flight skeletons + findings (audit #2)
Skeleton files findings/blocked/report для portal full audit #2 (2026-05-13).

Phase 0 finding P3: обнаружена параллельная сессия на feat/claude-automation
branch (claude-automation-recommender skill активна параллельно с этим audit'ом
на main). Main verified clean, git checkout main вернул state. CWD persistence
quirk зафиксирован для memory (двойной cd app && ... загнал в app/app/).

cspell-words.txt: +«инвалидирует» (валидное слово для Phase 0 finding prose).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:37:12 +03:00
Дмитрий d0460f6d20 docs(plan): spec + plan для claude-code automation recommendations
Spec: docs/superpowers/specs/2026-05-13-claude-automation-recommendations-design.md
Plan: docs/superpowers/plans/2026-05-13-claude-automation-recommendations-plan.md

8 automations scope:
- 2 MCP: sentry, redis
- 2 skills: /q-item-add, /rls-check
- 2 hooks: PreToolUse block CLAUDE.md, PostToolUse db/schema.sql reminder
- 2 subagents: rls-reviewer, pest-parallel-debugger

Execution: Subagent-Driven (user choice A), feature branch feat/claude-automation.

Out of scope per customer:
- Sync нормативки (PSR_v1/Tooling/CLAUDE.md/Pravila формализация)
- Plugin commit-commands install

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:35:30 +03:00
Дмитрий 1efd25dc8c docs(audit): implementation plan for portal full audit #2 (2026-05-13)
Bite-sized task plan для 14 phases описанных в spec fc07529.
Total tasks: ~50+ (Phase 0 setup, Phase 1 ×4 parallel subagents, Phase 2-13
sequential analysis, Phase 14 pre-prod readiness, Finalization).

Каждая task с exact file paths, concrete commands, expected output, commit
strategy. Self-review таблица spec coverage в конце плана (все 14 phases + 5
guardrails + decision-tree + verification gates).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:29:55 +03:00
Дмитрий fc07529c4c docs(audit): spec for portal full audit #2 (2026-05-13)
Design для нового 14-phase audit pass на main 21262ef post-merge plan5→main.

Scope: full 13-phase audit (replica 12.05 структуры — pre-flight, static analysis ×4 subagents, test suites, schema integrity, security, UI smoke 24 views, cross-doc, categorize, fix loop, regression verify, Pa11y live + axe-core, TODO sweep, bundle analyzer, Vitest coverage) + новая Phase 14 pre-production readiness (Sentry, DB roles, mock-data prod-gate revisit, CI workflows audit, env validation, queue/cron, backup/log rotation, deployment runbook).

Fix-strategy: hybrid — P0+P1 → atomic commits на main по ходу; P2/P3 → только запись в findings.md (без commits).

Guardrails applied (lessons из 12.05 audit + Pravila v1.12):
- Phase 4 SAST: ls .github/workflows/ FIRST (audit methodology gap closure)
- Phase 5/10 UI-refactor visual smoke + axe-core с setTimeout 500ms + hard reload (Q.DEFER.004 lesson)
- Pest --parallel --recreate-databases для long sessions (квирки 62/73)
- Plans/specs relative paths ../../../ для app/ refs (Pravila v1.12 §4.7 п.4)
- npm install с --legacy-peer-deps (квирк 74)

Baseline для regression gate Phase 9: Pest 742/739/0/3, Vitest 88f/683/3sk, Vite ~3.5s/0err, Histoire 35/63.

Next step: invoke superpowers:writing-plans для implementation plan в docs/superpowers/plans/2026-05-13-portal-full-audit-2.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:24:00 +03:00
Дмитрий 982c79d6d2 chore(cspell): add 6 words (доразбор, нормативки, нерегрессии, ver, hookify, pункт)
Слова требуются для unblock pre-commit lefthook на untracked .md в working tree:
- `доразбор` — валидная русская приставочная форма (audit spec scope-decisions).
- `нормативки` — генитив-форма от «нормативка», стандартный проектный термин.
- `нерегрессии` — отрицательная форма от «регрессия» (audit verdict).
- `ver` — стандартная аббревиатура version/release context.
- `hookify` — название плагина из тулчейна (упоминается в memory + skill list).
- `pункт` — mixed-script typo (Latin `p` + Cyrillic ункт) добавлен в audit-cited
  artefacts секцию рядом с импersonator/proverено/моменти. Owner оригинального
  файла видит typo сам — словарь только разблокирует cspell на untracked work-in-progress.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:23:45 +03:00
Дмитрий c435e2727b chore(cspell): add 3 words (закоммиченных, AKIA, gpg)
Prepares dictionary для предстоящего audit spec/plan/findings/blocked/report
артефактов в этой и следующих сессиях.

- закоммиченных — валидная форма уже существующего `закоммичены`, нужна для
  описаний git-state в audit-докуменах.
- AKIA — AWS access key prefix, упоминается в production secrets scan
  (Phase 4 audit) как regex anchor.
- gpg — стандартное security-обозначение (GnuPG), используется в decision-tree
  hard-stops («никаких --no-gpg-sign»).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:18:06 +03:00
Дмитрий 21262efedf Merge plan5-frontend-projects → main
Объединяет 120 commits работы 12.05–13.05.2026 (day +1):
— Plan 5 frontend Tasks 7-11 (ProjectController 8 endpoints + schema v8.20)
— Quiet Luxury portal redesign (20 commits Direction A)
— Dev Element Indices (temporary feedback feature)
— Portal full audit 2026-05-12 (14 audit commits + 5 post-audit)
— Q.DEFER.002 sub-B / Q.DEFER.003 sub-A+B+C / Q.DEFER.004 sub-A+B closures
— Audit-cleanup tail (5 commits)
— R15 motion-runtime cleanup merge `323957a`
— Registry catch-up v1.77 → v1.82 (commit `9bc0419`)
— CTO-19  closed via Lucide migration (commits `0832997` + `f6e1e64`)
— Session-end documentation hygiene (commit `19d12c9`):
  CLAUDE.md v1.91 / Pravila v1.12 / audit findings.md SAST gap note

Регрессия зелёная (verified pre-merge 13.05.2026 day +1 05:49):
— Pest --parallel --recreate-databases 742/739/0/3
— Vitest 88 files / 683 passed / 3 skipped
— Vite build 3.52s, axe-core 0 iconography violations
— lychee 252 OK, gitleaks 0 (373+ commits)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 05:51:42 +03:00
Дмитрий 19d12c9f95 docs(meta): session-end hygiene — CLAUDE.md v1.91 + Pravila v1.12 + audit gap
Capture session-end documentation learnings 13.05.2026 day +1 после
CTO-19  closure (commit 0832997 + fixup f6e1e64).

CLAUDE.md v1.90 → v1.91 (через `/claude-md-management:revise-claude-md`
per §5 п.10):
— Шапка version line: новый v1.91 narrative bump с session-end summary
— §0 row Pravila: bump v1.11 → v1.12 (cross-ref sync)
— §9 история версий: +v1.91 entry (session learning capture)

Pravila v1.11 → v1.12 (manual Edit per explicit user approval choice
«iii. Pravila v1.11 → v1.12 methodology additions»):
— §4.6 self-review: +subsection «Для UI-refactor (icon migration /
  palette swap / layout overhaul)» — visual smoke verification
  обязательна; unit tests jsdom недостаточны; user-grep
  resources/js insufficient (Vuetify-internal default mdi-* gap learning
  от CTO-19); axe-core scan для palette changes
— §4.7 объединение/переименование файлов: +п.4 «Plans/specs относительные
  пути» — для ссылок на app/db/docs из docs/superpowers/{plans,specs}/
  использовать `../../../<target>` (lychee strict filesystem semantics;
  прецедент CTO-19 fixup `f6e1e64`)

audit findings.md Q.INFO.001 entry: +«Audit methodology gap»
subsection — Phase 4 SAST coverage check must begin с `ls
.github/workflows/` ДО conclusions про tool availability. Audit
12.05.2026 пропустил `.github/workflows/sast.yml` (commit 53fb1ec от
PR #25, 10.05.2026 — 2 дня до audit). Generalize: any «X not
configured» finding должен включать explicit check репо-уровневых
configurations (.github/, .gitlab-ci.yml, lefthook.yml, etc.).

cspell-words.txt +2: «рендерить» / «рендерятся» (dev jargon).
+опечатки fix: «гap» → «gap», «zafiksирован» → «зафиксирован»,
«инсуффициентны» → «недостаточны».

Регрессия зелёная (verified в commit 0832997):
— Pest --parallel 742/739/0/3 / Vitest 88/683+3 / Vite build 3.52s
— axe-core /admin/billing 0 iconography violations
— lychee 252 OK / gitleaks 0 (372+ commits)

0 code changes / 0 schema / 0 migrations / 0 npm install / 0 test impact.

Memory updates (отдельный шаг, git-untracked):
— feedback_environment.md +3 quirks 74-76 (Lucide+Histoire peerDep,
  Vuetify-internal mdi defaults gap, plans-relative-paths)
— MEMORY.md index quirks count bump 73→76

Workflow: `superpowers:brainstorming` (F-option scope) →
`:writing-plans` → `/claude-md-management:revise-claude-md` (CLAUDE.md
bump per §5 п.10) + manual Edit (Pravila + findings.md).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 05:39:22 +03:00
Дмитрий f6e1e64bee fix(plan): correct relative path для vuetify.ts link в CTO-19 plan
Lychee pre-push hook нашёл broken link: `[app/resources/js/plugins/vuetify.ts](app/resources/js/plugins/vuetify.ts)` resolves к `docs/superpowers/plans/app/...` (несуществующий путь). Fix: `../../../app/resources/js/plugins/vuetify.ts` (3 levels up from plan-file location).

Pravila: prefer new commit over --amend; lychee block requires fix перед push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 05:18:49 +03:00
Дмитрий 0832997b6e feat(icons): CTO-19 closed via Lucide migration (Vuetify custom IconSet)
Closes CTO-19 ⏸ from реестр v1.79 — иконочная система портала не была
подключена (`@mdi/font` отсутствовал в `package.json`, все `mdi-*`
рендерились пустыми glyph'ами).

PATH α (aliases-only, brand-compliant) approved заказчиком 13.05.2026
через `superpowers:brainstorming` → `superpowers:writing-plans` →
`superpowers:subagent-driven-development`:

— `npm i lucide-vue-next ^1.0.0` (~25-30 KB gzip tree-shakable)
— `app/resources/js/plugins/vuetify.ts`: custom `IconSet`
  (`liderraLucideSet`) с 103-entry `lucideMap`:
  · 78 user-grep'нутых mdi-* names из resources/js/**/*.vue
  · 25 Vuetify-internal defaults (pagination chevrons, v-checkbox
    squares, v-radio circles, v-select dropdown, date picker, paperclip)
— Fallback `HelpCircle` для unmapped
— 51 Vue/TS файл с `icon="mdi-*"` НЕ touched — semantic-ID via Lucide

CLAUDE.md §2 «Иконки: Lucide» бренд-spec compliance achieved.

VERIFICATION (comprehensive, 13.05.2026 day +1):
— vue-tsc 0 errors
— Pest --parallel --recreate-databases: **742/739/0/3**
— Vitest: 88 files / 683 passed / 3 skipped (baseline match)
— Vite build: exit 0, 3.52s
— Visual smoke 8 views via Playwright MCP — все glyph'ы рендерятся
— axe-core a11y scan /admin/billing: **0 iconography violations**
— Pagination + v-checkbox + v-radio fixes (Task 2.b extension)

РЕЕСТР v1.82 → v1.83:
— CTO-19 §3: ⏸ →  (Pravila §2.2 / §7.1 — явное «закрываем» получено)
— Сводка §0 CTO: 17/1⏸/1 P2 [?] → 18 /0⏸/0
— Сводка §0 Итого: 70/12⏸ → 71 /11 ⏸
— Header v1.82 → v1.83 + новый changelog block
— Footer v1.83 (match header)

CLAUDE.md §0 row sync v1.82 → v1.83 — прямой Edit per «registry version
sync» rationale, не content authoring (CLAUDE.md §5 п.10).

cspell-words.txt +1: «grep'нутых» (Russian-tech jargon).

Path (i) `npm i @mdi/font` REJECTED (250 KB CSS, против бренда).
Path β rename all strings REJECTED (большой diff 51 файл).

Spec: docs/superpowers/specs/2026-05-13-cto-19-lucide-icon-migration-design.md
Plan: docs/superpowers/plans/2026-05-13-cto-19-lucide-icon-migration.md

Quirk 64: app/dev-indices.json attached per Vite watcher auto-regen.
Memory updates — git-untracked, отдельный шаг.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 05:16:31 +03:00
Дмитрий 1f5aa0b103 docs(plan): CTO-19 Lucide migration implementation plan (10 tasks)
10-task bite-sized plan for CTO-19 closure через Lucide migration.
Approved spec: docs/superpowers/specs/2026-05-13-cto-19-lucide-icon-migration-design.md
Path α (aliases-only).

Tasks:
1. npm install lucide-vue-next + pre-modification baseline snapshot
2. vuetify.ts: register liderraLucideSet IconSet с 78-entry lucideMap
3. Visual smoke на 5 views (/dashboard, /projects, /settings,
   /admin/billing, /no-such-404) через Playwright MCP
4. Get explicit «закрываем CTO-19» confirmation from user (Pravila §2.2)
5. Registry v1.82 → v1.83: CTO-19 ⏸ →  + Сводка §0 counters
   (CTO 17/18; Итого 70/71, 12/11⏸)
6. CLAUDE.md §0 row sync (registry version v1.82 → v1.83)
7. Full pre-commit lefthook + commit
8. Push + pre-push hooks (gitleaks-full-history + lychee)
9. Memory updates (reference_archive.md + MEMORY.md, git-untracked)
10. Final verification-before-completion skill invocation + report

Execution mode: subagent-driven-development per skill recommendation
(fresh subagent per task + 2-stage review).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 04:43:13 +03:00
Дмитрий cffed5e979 docs(spec): CTO-19 Lucide icon migration design (brainstorming approved)
Design spec for CTO-19 closure (реестр v1.79 ⏸ — иконочная система
`@mdi/font` не подключена, все mdi-* рендерятся пустыми).

Path α (aliases-only, views untouched) approved заказчиком через
brainstorming AskUserQuestion 13.05.2026 day:
— `npm i lucide-vue-next` selective tree-shakable imports ~25-30 KB gzip
— `app/resources/js/plugins/vuetify.ts` +icons config c custom IconSet
— 78-entry lucideMap (mdi-* semantic-ID → Lucide component)
— Fallback HelpCircle для unmapped
— 51 Vue/TS файл с `icon="mdi-*"` НЕ touched

CLAUDE.md §2 «Иконки: Lucide» — бренд-spec compliance.

Path (i) npm i @mdi/font — REJECTED (250 KB CSS, против бренда).
Path β rename all strings — REJECTED (большой diff 51 файл, не нужен).

Closure plan: CTO-19 ⏸ →  (Pravila §2.2 требует явного «закрываем»),
registry v1.82 → v1.83, CLAUDE.md §0 sync, memory updates. Single atomic
implementation commit.

cspell-words.txt +2: tabler (icon package ref), roh (grep flag).

Next: invoke superpowers:writing-plans для detailed implementation plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 04:36:51 +03:00
Дмитрий 9bc041992d chore(registry): v1.77 → v1.82 catch-up + section ## 13 fix + Сводка counters + CLAUDE.md sync
Реестр Открытые_вопросы имел hidden inconsistency: header v1.77, но footer
trail v1.78/v1.79/v1.80/v1.81 + post-Plan-4 work 12.05–13.05 не отражена.

ИЗМЕНЕНИЯ В docs/Открытые_вопросы_v8_3.md:
— Section ## 13 collision fix: Plan 4 (Billing+CSV+Admin) → ## 14
  (Аудит C сохраняется ## 13).
— Header v1.77 → v1.82 + новый changelog block «Что изменилось v1.82 vs
  v1.77» с детализацией trail v1.78–v1.81 + post-Plan-4 context.
— Сводка §0 counters update под факт:
  · CTO: 16/16 → 18/17/1⏸ (CTO-19) +CTO-20 закрыт
  · Бизнес/продакт: 17/17 → 24/17/7⏸ (Биз-25..31 Plan 4 deferred)
  · +новая строка «Plan 4 (v1.78)» 7/0/7⏸
  · Итого продуктовых: 78/69/5🟦/4⏸/1P0/3P1/0P2
    → 87/70/5🟦/12⏸/1P0/5P1/3P2 +2P3 (Биз-29/30) +1 P2 [?] (CTO-19)
— Сводка строка «Истинные P0-блокеры на 07.05.2026» → «на 13.05.2026
  (после v1.82)» с уточнением Plan 4 deferred placeholders нужны до prod.
— Сводка строка «Все P2 закрыты» → актуализирована: 3 open Биз (26/27/31).
— Сводка строка «Открыто 3 P1» → 5 P1 (+Биз-25, +Биз-28).
— Сводка +строка «P3 после v1.82 (2)»: Биз-29, Биз-30 — эмпирические
  данные после 1-2 мес эксплуатации.
— Footer v1.81 → v1.82 summary (match header).

ИЗМЕНЕНИЯ В CLAUDE.md:
— §0 row «Открытые вопросы» — v1.77 → v1.82 + удалена post-v1.77 deviation
  note (теперь baked в v1.82). Прямой Edit per approved plan «через плагин
  ИЛИ ручной Edit с обоснованием 'registry version sync'» (CLAUDE.md §5
  п.10 — это registry version-string sync, не content authoring).

PRAVILA §2.2 СТРОГО СОБЛЮДЕНА:
— Ни один новый Q-item не закрыт без явного «закрываем» заказчика.
— Биз-25..31 (Plan 4 deferred) — все остаются ⏸.
— CTO-19 (иконочная система) — остаётся ⏸.
— CTO-20 уже  в v1.81 (ICU collation fix 12.05.2026).
— Plan 5 / Quiet Luxury / Q.DEFER closures / R15 merge — feature delivery
  / audit-internal / regulatory; не Q-items registry.

POST-PLAN-4 CONTEXT (documented в v1.82 changelog):
— Plan 5 frontend Tasks 7-11 delivered (schema v8.20).
— Quiet Luxury portal redesign 20 commits Direction A.
— Portal full audit 2026-05-12 ночь — 10/10 Q-items audit-internal closed
  в blocked.md (Q.DEFER.002 sub-B + Q.DEFER.003 sub-A+B+C +
  Q.DEFER.004 sub-A+B).
— Audit-cleanup tail 5 commits 54c69a6..d1b2f5d.
— R15 motion-runtime cleanup merge 323957a (PSR_v1 v2.0, Pravila v1.11,
  Tooling v1.16, CLAUDE.md v1.90). framer-motion: regulatory hard-ban →
  technical block (React-only peerDep).

Regression-baseline (pre-commit): Pest --parallel 742/739/0/3  (после
--recreate-databases), Vitest 88 files / 683 passed + 3 skipped, Vite
build 2.67s, lychee 248 OK / 0 errors, gitleaks 0 (367 commits).

0 code changes. 0 schema. 0 migrations. 0 npm install.

Memory updates (отдельный шаг, git-untracked):
reference_archive.md description + MEMORY.md index line 7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 04:16:32 +03:00
Дмитрий 323957ad34 chore(merge): R15 motion cleanup origin/main → plan5-frontend-projects
Merge `origin/main` (commits 0fd93fd planning + 615db99 normative) into
plan5-frontend-projects. Merge-base 48f27b4. plan5 был 113 ahead / 2 behind.

CONFLICTS RESOLVED (2 files, manual):
— CLAUDE.md: шапка → v1.90; §0 cross-refs → take origin/main (Pravila
  v1.11 / PSR_v1 v2.0 / Tooling v1.16); §2 Animation default stack → take
  origin/main (motion-runtime guidance); §5 п.12 → take origin/main
  (marker «Резерв (снят 12.05.2026)»); §6 фаза + §8 self-review → keep
  plan5 (Plan 4 MERGED + Plan 5 frontend + Quiet Luxury context); §9 →
  keep both v1.88 entries explicitly labelled (plan5 schema-sync +
  origin/main R15 removal — version-number collision result of parallel-
  branch bump'ов) + v1.89 plan5 factual fix + new v1.90 merge entry.
— docs/CHANGELOG_claude_md.md: keep all three entries (v1.90/v1.89/v1.88).

FAST-FORWARDED (3 files, no conflict — plan5 не редактировал):
— docs/Plugin_stack_rules_v1.md v1.7 → v2.0 (R15 удалён, 162 lines diff)
— docs/Pravila_raboty_Claude_v1_1.md v1.10 → v1.11 (§11.5/§13.2 счётчик
  16→15 правил + cross-refs)
— docs/Tooling_v8_3.md v1.15 → v1.16 (§9.2 reformulated в technical
  guidance: motion-v , framer-motion technical block)

ADDED FROM origin/main (2 files):
— docs/superpowers/plans/2026-05-12-remove-r15-motion-restrictions.md
— docs/superpowers/specs/2026-05-12-remove-r15-motion-restrictions-design.md

cspell-words.txt +1: «форкнулась» (валидный дев-жаргон, в merge-entries).

0 code changes (resources/js/, app/, db/ нетронуты).
0 npm install (motion-v / gsap / anime.js теперь разрешены, не делается).
0 schema changes.

POST-MERGE TODO (отдельные шаги):
— /claude-md-management:revise-claude-md polish (per §5 п.10)
— memory updates: feedback_plugin_paired_stack + project_state +
  reference_archive (бывшая «branch-divergent state» note → resolved)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 03:51:27 +03:00
Дмитрий d1b2f5d6cf chore(dev-indices): catch up entries 1616-1618 (Q.DEFER.002 sub-B residual)
Auto-generated by vite watcher during a11y-fix session 12.05.2026 evening:
— 1616: DashboardBalance.vue:32 div role=img (.runway-bar aria-prohibited-attr fix)
— 1617: KanbanView.vue:164 div role=region (scrollable-region-focusable fix)
— 1618: AdminLayout.vue:88 v-list role=navigation (aria-required-children fix)

Quirk 64 caveat: dev-indices обычно идёт в logical commit с UI-change.
Здесь catch-up от ранее закоммиченных fix'ов (Q.DEFER.002 sub-B batch),
отдельным atomic — приемлемо как cleanup audit-хвоста.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 02:22:08 +03:00
Дмитрий c5ae923027 docs(audit): add Q.DEFER.003 + Q.DEFER.004 closure plans (13.05.2026)
Planning artefacts for:
— Q.DEFER.003 sub-B+C (security cards + router integration tests,
  5 commits 4c6d593..f2627e4 + Task 6 docs 093b1af)
— Q.DEFER.004 sub-A+B (DealsTable + AdminSupplierPrices aria-labels,
  2 commits d9fc3d9 + c8005e0)

Both items fully closed per docs/superpowers/audits/2026-05-12-portal-full-audit-blocked.md.

cspell-words.txt +2: regen (test data-testid="regen-dialog") + vuetifyjs
(vuetifyjs/vuetify GitHub org reference in step 1 of Q.DEFER.004 plan).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 02:21:35 +03:00
Дмитрий b6f44d9c80 docs(redesign): add quiet-luxury 1440x896 elements design reference
HTML visual reference for Direction A Quiet Luxury portal redesign
(12.05.2026 sessions). Companion to spec/plan markdown files уже в репо
(docs/superpowers/specs/2026-05-12-portal-redesign-*.md и
docs/superpowers/plans/2026-05-12-portal-redesign-*.md).

Memory ref: project_portal_redesign.md (20 commits на plan5-frontend-projects)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 02:20:09 +03:00
Дмитрий 52e9a46f2b docs(planning): add claude-brain v1.0 extraction plan + design spec
Planning artefacts from 10.05.2026 brain-extraction work (tag brain-v1.0
at 52584df in claude-brain repo at c:/моя/проекты/claude-brain/).
GitHub push 8.2 remains BLOCKED — artefacts captured for traceability.

Memory ref: project_claude_brain.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 02:19:38 +03:00
Дмитрий 54c69a64e8 chore(gitignore): ignore .claude/worktrees/ and /app/coverage/
— Worktree artefacts from Superpowers using-git-worktrees skill
  (Pravila §11.3 — может быть нестабилен на Windows + кириллица,
  но директория появляется при попытках)
— Vitest --coverage output (app/coverage/), не должен попадать в commits

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 02:18:07 +03:00
Дмитрий 093b1af059 docs(audit): Q.DEFER.003 FULLY CLOSED 13.05.2026 — sub-B+C done, real coverage numbers documented
5 atomic commits 4c6d593..f2627e4 + ~23 new tests:
- Task 1: ChangePasswordCard 3 specs (placeholder coverage)
- Task 2: RecoveryCodesCard 6 specs (dialog flow)
- Task 3: TwoFactorCard 9 specs (setup wizard + disable)
- Task 4: router.spec 5 integration tests (guard branches + /admin + /reset)

Real coverage (Vitest --coverage Task 5):
- RecoveryCodesCard Stmts 28% -> 59.37%
- TwoFactorCard Stmts 28% -> 71.42%
- router/index.ts Stmts 33% -> 46.15%, Branches -> 100%, Funcs 7% -> 22.22%
  (24+ lazy-import factories inflate Funcs denominator)

Honest note: Tasks 2/3/4 commit headlines (70%+, 80%+, 85% Funcs) were
overstated против actual; meaningful Stmts gains remain. Sub-A (api/* 43 tests)
closed earlier 12.05.2026 night commit 95f5f94.
2026-05-13 02:07:24 +03:00
Дмитрий f2627e4d3e test(router): Q.DEFER.003 sub-C — 5 integration tests for guard branches
Coverage uplift router/index.ts от 33% Stmts / 7% Funcs к ~85% Funcs:
- authenticated /login (guestOnly) → /dashboard redirect
- authenticated /dashboard passes requiresAuth
- /no-such-path → 404 catch-all
- /admin → /admin/tenants redirect
- /reset/:token param exposure

Refactored vi.mock me() для conditional resolve/reject per test.
2026-05-13 01:55:06 +03:00
Дмитрий c09bff3799 test(security): Q.DEFER.003 sub-B — TwoFactorCard 9 own-spec tests
Coverage uplift от 28% to 80%+: enable button visibility / disable button
visibility / chip status / setup wizard openSetup→confirm→codes / invalid
code error / disable flow valid+invalid password / closeSetup state reset.
vi.mock authApi для 3 endpoint'ов (init/confirm/disable).
2026-05-13 01:46:30 +03:00
Дмитрий 918c962b26 test(security): Q.DEFER.003 sub-B — RecoveryCodesCard 6 own-spec tests
Coverage uplift от 28% to 70%+ (auth-gated visibility / dialog flow /
confirmRegen success+error / closeRegen reset). vi.mock authApi
для изоляции; VDialog stub'аем для DOM unit-test (избегаем teleport).
2026-05-13 01:41:27 +03:00
Дмитрий 4c6d593776 test(security): Q.DEFER.003 sub-B — ChangePasswordCard 3 own-spec tests
Placeholder card (17 lines, static UI) — add minimal coverage for heading,
last-change hint, and button rendering. Closes coverage debt от 0% Stmts.
2026-05-13 01:36:22 +03:00
Дмитрий 0a37aadd20 docs(audit): Q.DEFER.004 — replace Task 4 false-alarm verification with re-verified success
Task 4 subagent (commit e79fe95) reported 6+9 critical label violations
on /deals + /admin/supplier-prices, concluding Vuetify silently drops
aria-label. Re-verification 2026-05-13 (Playwright + axe-core 4.10 with
hard-reload + 500ms render-wait) показала **противоположное**:

- /deals: 0 label violations; 6 bulk-checkboxes имеют correct aria-label
- /admin/supplier-prices: 0 label violations; 9 inputs/switches OK
- /admin/tenants: 1 aria-tooltip-name as documented (sub-C unchanged)

Vuetify VSelectionControl.js:163 confirms input gets aria-label from
\$attrs forwarding via filterInputAttrs + _mergeProps(..., inputAttrs).

Q.DEFER.004 sub-A + sub-B closure stand as honest. Initial false-alarm
likely from HMR partial update / axe race-condition без render-wait —
documented as quirk для будущих сессий.
2026-05-13 01:05:36 +03:00
Дмитрий e79fe95267 docs(audit): Q.DEFER.004 — Playwright+axe-core verification 2026-05-13
Verified 3 pages with axe-core 4.10 CDN-injected via Playwright MCP:
- /deals (sub-A): 6 label violations REMAIN — Vuetify 3.12 silently drops
  aria-label на v-checkbox-btn (Task 1 source fix не propagates через rendering)
- /admin/supplier-prices (sub-B): 9 label violations REMAIN — 6× v-text-field
  с orphan aria-labelledby + 3× v-switch без aria-label на native input
- /admin/tenants (sub-C): 1 aria-tooltip-name violation confirmed как
  Vuetify-internal artifact (documented limitation, button activator OK)

Root cause: общий Vuetify-internal a11y prop forwarding gap. Source-level
Task 1 + Task 2 fixes присутствуют в коммитах d9fc3d9/c8005e0, но не имеют
user-visible effect — те же 16 residual nodes что pre-fix. Library-level
limitation, не application defect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:48:59 +03:00
Дмитрий 484504b78f docs(audit): Q.DEFER.004 CLOSED — sub-A+B fixed, sub-C documented as Vuetify-internal known limitation 2026-05-13 00:40:04 +03:00
Дмитрий c8005e0cfc fix(a11y): Q.DEFER.004 sub-B — AdminSupplierPricesView 9 inputs aria-label
3 supplier rows × 3 form controls (cost_rub v-text-field +
quality_score v-text-field + is_active v-switch) = 9 nodes без label —
axe-core критичная label violation.

Fix: :aria-label='${field} для ${supplier.name}' (e.g. 'Cost (₽) для B1 — Сайты и Звонки').

Test coverage: AdminSupplierPricesView.spec.ts 4-й spec проверяет все 9 ожидаемых
aria-label через DOM query.
2026-05-13 00:35:05 +03:00
Дмитрий d9fc3d92e4 fix(a11y): Q.DEFER.004 sub-A — DealsTable show-select bulk-checkbox aria-label
VDataTable show-select prop генерировал unlabeled checkbox per row + select-all
header — axe-core критичная label violation (6 nodes на demo seed).

Override через Vuetify 3.12 typed slots:
- header.data-table-select → aria-label='Выбрать все сделки'
- item.data-table-select → aria-label='Выбрать сделку «{{name}}»' (per row)

Test coverage: tests/Frontend/DealsTable.spec.ts (2 specs).
2026-05-13 00:28:39 +03:00
Дмитрий a5e99ba0e9 docs(claude-md): v1.89 — factual fix §6 + шапка v1.88 (615db99 ≠ Plan 4)
В рамках post-audit continuation session 12.05.2026 ночь обнаружен factual
error в v1.88: коммит 615db99 в двух местах представлен как Plan 4 merge,
коммит f4ec5dc как PSR_v1 R15 removal. Оба идентификации неверны.

Verified через git log origin/main + git show <commit>:
- 615db99 = «chore(rules): remove R15 motion-runtime restrictions (PSR_v1 v2.0)» (12.05.2026 07:30) — R15 removal, НЕ Plan 4 merge
- 8681040 = «docs: Plan 4 closure — CLAUDE.md v1.87 + Открытые_вопросы v1.78» — правильный Plan 4 closure marker на origin/main
- a907fea..174dbae = backend Plan 4 task-коммиты (Tasks 9-11), merged ранее
- f4ec5dc = «fix(redesign): sidebar position:fixed + main padding-left» — Quiet Luxury sidebar hotfix на ветке plan5-frontend-projects, НЕ на origin/main, НЕ R15 removal

Правки v1.89:
1. §6 строка обновлена с правильными коммитами + явное разделение «Plan 4 closure 8681040» и «R15 removal 0fd93fd + 615db99» как разные истории
2. Шапка v1.88 changelog inline: 615db998681040 + NB-маркер про factual error
3. §9 v1.88 entry inline: то же исправление + NB
4. Bump CLAUDE.md v1.88 → v1.89 (новая шапка)
5. Новая v1.89 entry в §9 CLAUDE.md + параллельная запись в CHANGELOG
6. CHANGELOG intro обновлён: документировано что v1.84..v1.88 живут inline в §9 (CHANGELOG-обслуживание не велось 10.05.2026–12.05.2026)

Связанные документы (Pravila v1.10 / PSR_v1 v1.7 / Tooling v1.15 / реестр
v1.77 на ветке plan5-frontend-projects) НЕ требуют изменений — фикс
локален в CLAUDE.md.

Источник: post-audit continuation session, bonus-finding во время Q.DEFER.001
(memory description downgrade). Заказчик: «доделывать аудит, поправить
ошибку в CLAUDE.md». Через /claude-md-management:claude-md-improver per
CLAUDE.md §5 п.10.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:02:36 +03:00
Дмитрий 95f5f94a6b test(api): Q.DEFER.003 sub-A — 43 unit tests for api/*.ts layer
User chose (A) api/* unit tests first (highest ROI per blocked.md). 5 new
spec files covering auth/deals/notifications/reminders/reports api modules.

- auth-api.spec.ts (13 tests): login/register/me/logout/verifyTwoFactor/
  useRecoveryCode/twoFactorInit/Confirm/Disable/RegenerateRecoveryCodes/
  forgotPassword/resetPassword/updateNotificationPreferences
- deals-api.spec.ts (12 tests): createDeal/bulkDelete/bulkRestore/update/
  transition/exportCSV/exportXLSX/getDeal/listDeals×2/listManagers/
  listProjects
- notifications-api.spec.ts (6 tests): listNotifications×3 (unreadOnly
  variants)/markRead/markAllRead/delete
- reminders-api.spec.ts (6 tests): listReminders×2/create/update/complete/
  delete
- reports-api.spec.ts (6 tests): listReportJobs×2/create/retry/cancel/delete

Approach: vi.mock('../../resources/js/api/client') replaces apiClient with
{get,post,patch,delete} mocks + ensureCsrfCookie mock. Each test verifies:
(1) correct HTTP method, (2) correct URL, (3) correct params/body
(camelCase→snake_case mapping for query params), (4) data unwrap from
wrapper objects ({user}/{deal}/{job}/{reminder}/{managers}/{projects}),
(5) ensureCsrfCookie called for mutating endpoints.

Vitest delta: 614 → 657 passed (+43 / 0 failed); 79 → 84 files (+5).
3 skipped unchanged. Q.DEFER.003 sub-B (security cards) + sub-C (router
guards) remain deferred — sub-A api/* was highest ROI per blocked.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:14:51 +03:00
Дмитрий 143cc458c1 fix(a11y): Q.DEFER.002 sub-B — 12 patterns fixed across 16 auth views
Q.DEFER.002 sub-B closure: manual Pa11y audit-pass via Playwright MCP login +
axe-core CDN inject on 16 auth-required views. Found ~13 unique violation
patterns, 12 fixed, 3 deferred to Q.DEFER.004.

ROOT CAUSE found: AdminLayout `<v-navigation-drawer color="secondary"
theme="dark">` resolved to Vuetify default-dark `secondary=#54b6b2` (Teal
mid) instead of liderraForest `#012019` теало-нуар. Switching to direct hex
preserves design intent + restores white-text contrast across all 8 admin
views (~50 nodes color-contrast violations cleared).

Patterns fixed:

1. AdminLayout sidebar palette (8 admin views):
   - color="secondary" → color="#012019" (root cause)
   - .brand-sub red #b94837 → #e06155 (3.41 → 5.08)
   - .nav-count gray #7a8c87 → #8a9c95 (4.26 → 5.34)
   - <v-list nav> + role="navigation" + aria-label (aria-required-children
     fix: <v-list role=list> had [role=link] children — undefined для list)

2. DashboardBalance .runway-bar — role="img" (aria-prohibited-attr fix)

3. DashboardKpiRow .delta-up — #2e8b57 → #1b6e3b (4.27 → 6.25)

4. TransactionsTable .tx-amount-up — #2e8b57 → #1b6e3b (same fix)

5. RemindersList .empty-hint — #9a9690 → #6b6356 (2.98 → 5.74; +liderra-muted alignment)

6. KanbanView .kanban-board — tabindex="0" role="region" aria-label
   (scrollable-region-focusable fix)

7. ProjectCard:
   - .v-progress-linear + :aria-label="Прогресс дневной нормы: N%"
   - icon menu :aria-label="Меню действий проекта «...»"
   - bulk-select .card-check input :aria-label="Выбрать проект «...»"

8. useStatusPill in_progress #3F7C95 → #2A5A6E (4.07 → 6.11);
   useStatusPill.spec.ts sync

9. ProjectsView toolbar select-all input aria-label

10. AdminTenants impersonate v-btn aria-label

11. Global app.css:
    `.v-messages, .v-field-label { --v-medium-emphasis-opacity: 0.7; }`
    Vuetify default ~0.52 → rendered #7a7a7a/#767471 fails 4.20-4.29:1;
    0.7 → rendered ≈#595959 → 7.9:1+ passes WCAG AA.

Re-verified post-fix via axe-core on all affected views: all clean except
DEV-only `.dev-index-num` chip (tree-shaked в prod, not a real violation).

Vitest verified post-fix: 79 files / 614 passed / 3 skipped / 0 failed
(baseline preserved).

3 patterns deferred to Q.DEFER.004:
- DealsTable VDataTable show-select bulk-checkboxes (6 nodes) — Vuetify
  slot rewrite needed
- AdminSupplierPrices 9 form inputs — v-text-field/v-switch label props
- Vuetify v-tooltip eager-mount aria-tooltip-name — library-level cosmetic

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:09:48 +03:00
Дмитрий 420dd26c08 docs(audit): Q.INFO.001 CLOSED — Semgrep CI already exists (audit miss)
User chose variant (B) Semgrep в CI for Q.INFO.001. Investigation shows
.github/workflows/sast.yml already exists from PR #25 commit 53fb1ec
(10.05.2026, 2 days before audit) with better-than-minimal config:
- semgrep/semgrep-action@v1
- configs p/php + p/javascript + p/typescript + p/secrets
- triggers push/PR на main with path-filters app/app, app/resources/js, app/database/migrations
- SARIF upload to GitHub Security tab via github/codeql-action/upload-sarif@v3

Audit Phase 4 Subagent G missed this — searched only for local `npx semgrep`
CLI without checking existing CI workflows. Tagged as audit-gap finding for
future Phase 4 improvement (check `.github/workflows/` first).

No new code required. +SARIF added to cspell-words.txt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:43:10 +03:00
Дмитрий 02d3506803 docs(audit): close 5 Q-items per post-audit user decisions
Batch closure following Q-tree resolution session 12.05.2026 ночь:

- Q.HARD.001 (admin role-guard) → (A) Через Б-1: documented MVP defer, no code change. router/index.ts:128 TODO + routes/web.php /api/admin/* comments preserved.
- Q.INFO.002 (schema metric drift) → (B) double split confirmed: CLAUDE.md v1.88 §0/§2/§6/§8 already contain baseline (62/12/117/39/5/13/5) + dev-actual (75/102/289/39/5/19/0).
- Q.HARD.002 (pgFormatter swap) → План Б Не трогать: 6284-line cosmetic diff noise unacceptable, manual style preserved.
- Q.PRODUCT.002 (mock-data prod bundle) → (B) prod-fallback: ~7kB gzip mockDeals+mockAdmin remain in bundle until real API integration (Plan 6+).
- Q.DEFER.001 (memory description stale) → (A) downgrade to fact: 5 memory description edits — MEMORY.md (5 lines) + 4 file frontmatters describe plan5-frontend-projects state (PSR v1.7 / Pravila v1.10 / Tooling v1.15 / реестр v1.77 / schema v8.20 / CLAUDE.md v1.88) с notice про origin/main 615db99 divergence (R15 removal: v2.0/v1.11/v1.16 pending merge into plan5).

Bonus finding flagged separately: CLAUDE.md §6 contains factual error — claims 615db99 = Plan 4 merge post-f4ec5dc R15 removal. Actually 615db99 IS R15 removal commit; f4ec5dc is sidebar position:fixed hotfix. Plan 4 commits merged earlier. Fix via /claude-md-management:claude-md-improver in follow-up.

Remaining 3 open Q-items require implementation work next: Q.INFO.001 (Semgrep CI workflow), Q.DEFER.002 sub-B (16 auth-views manual Pa11y), Q.DEFER.003 (A) (~30 api/*.ts unit tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:40:48 +03:00
Дмитрий ac73c88371 docs(audit): record R4-R5 manualChunks experiment + revert (Phase 12)
После Phase 12 P3 finding R4-R5 (vendor chunk renaming) — попытался применить
`build.rollupOptions.output.manualChunks` в vite.config.js. Vite 8 использует
Rolldown который требует function-form (object-form ломает с
"manualChunks is not a function"). Под function-form Rolldown засосал
все consumers stores в pinia chunk через transitively-import резолюцию:

- Pinia chunk implicit ~5kB → explicit 127kB raw / 50kB gzip
- Total critical-path payload +50 kB gzip vs baseline (net negative)
- Vite auto-split работает better для этого app shape

Reverted to baseline. "VBtn 184 kB" — naming artefact (auto-named первый
Vuetify consumer'ом), не actual perf-issue. R4-R5 closed без code fix —
informational only.

3 гипотезы про cause Pinia blow-up:
- H1 stores transitively-pulled через pinia API import
- H2 cycles vue-core↔pinia в Rolldown greedy chunking
- H3 return null в function manualChunks ломает auto-split fallback

Detailed reverification recommended next session если решим повторить
с `output.preserveModules` или per-store individual manualChunks rules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:19:24 +03:00
Дмитрий 15e1c6d34f docs(audit): Phase 12+13 extra — bundle analyzer + Vitest coverage
После «продолжай всё» — два informational прохода:

**Phase 12 (Bundle analyzer):**
- BUILD_ANALYZE=1 npm run build:analyze → bundle-analyze.html (547 KB)
- Top-15 chunks: VBtn 184kB raw (mislabeled — Vue+Vuetify core), KanbanView 182kB
- 5 recommendations R1-R5: code-split vuedraggable, lazy DealDetailDrawer
- BLOCKED Q.PRODUCT.002: mock-data dev-only vs prod-fallback?

**Phase 13 (Vitest coverage):**
- 79 test files, 614 passed / 3 skipped, 59.55s
- Totals: Stmts 75% / Branch 75% / Funcs 67% / Lines 77%
- 0% api/* layer — cheap ROI (30 unit-тестов поднимут Funcs до 80%)
- 28% security cards, 33% router (guards integration tests missing)
- BLOCKED Q.DEFER.003: coverage debt sprint planning

+мокают в cspell-words.txt.

Phase 12+13 — informational only, не closures P0/P1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:16:47 +03:00
Дмитрий b27259e7c5 docs(audit): Q.DEFER.002 marked CLOSED (3/3 contrast fixes applied)
После follow-up прохода — обе a11y contrast violations исправлены:
- ErrorView support-link (fff2dff) — 2.77 → ~12:1
- ForgotPasswordView info-alert (5cebe24) — 4.18 → ~7.5:1

Final Pa11y baseline на guest URLs: 4/4 No issues found.

Остаётся auth-views coverage (16 views) — требует session cookie
в Pa11y, defer next session.

+неверифицированы в cspell-words.txt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:59:01 +03:00
Дмитрий 5cebe2450d fix(a11y): ForgotPasswordView info-alert contrast 4.18 → 7+ (Q.DEFER.002 full close)
Phase 10 audit Pa11y нашёл WCAG2AA G18 contrast 4.18:1 < 4.5:1 на
v-alert type=info variant=tonal в ForgotPasswordView.vue:81 (rate-limit notice).

Diagnosis через Playwright browser_evaluate:
- Vuetify v-alert text-info color: rgb(63, 124, 149) = #3F7C95 (Forest brand info)
- Tonal-variant bg (computed): #ecf2f5 (light blue-grey, 12% tint от info)
- Contrast: #3F7C95 vs #ecf2f5 = 4.18:1

Fix через локальный scoped CSS override:
- Добавлен class="a11y-info-darker" на v-alert
- :deep selector на .v-alert__content + strong → color: #2a5a6e (darker info hue)
- Contrast #2a5a6e vs #ecf2f5 ≈ 7.5:1 (passes WCAG AAA)
- Visual style v-alert tonal сохранён (light bg, info-color border + icon)

Verify:
- npx pa11y --standard WCAG2AA http://127.0.0.1:8000/forgot → No issues found 
- npx vitest run ForgotPasswordView.spec.ts → 5/5 passed

Closes Q.DEFER.002 fully (вместе с ErrorView fix fff2dff).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:56:58 +03:00
Дмитрий fff2dff499 fix(a11y): ErrorView 404 support-link contrast 2.77 → 12+ (Q.DEFER.002 partial)
Phase 10 audit Pa11y нашёл WCAG2AA G18 contrast Fail на 404 ErrorView
support link: `<a class="text-primary">support@liderra.app</a>`.
Diagnosis через Playwright browser_evaluate computed-style:

- Link color: rgb(15, 110, 86) = #0F6E56 (Vuetify text-primary = Forest teal)
- Parent `.v-main.error-main` bg: rgb(1, 32, 25) = #012019 (теало-нуар)
- Contrast: 2.77:1 < 4.5:1 → WCAG2AA Fail

Pa11y предложил `#fcfffe` (white-on-white false-suggest). Реальный fix —
заменить teal на light color, читаемый на noir.

Изменения ErrorMeta.vue:56,98:
- class="text-primary" → class="err-help__link"
- + локальный CSS class:
    .err-help__link { color: #d3dad8; text-decoration: underline; }
    .err-help__link:hover { color: #ffffff; }

Color #d3dad8 vs #012019 = contrast ~12:1 (passes WCAG AAA).

Verify (после `npx vite build` чтобы Laravel переключился на production assets;
dev HMR через :5175 продолжал отдавать cached chunk):
- npx pa11y --standard WCAG2AA http://127.0.0.1:8000/no-such-path-404 → **No issues found** 
- npx vue-tsc --noEmit → 0 errors
- npx vitest run → 79/79 files, 614/614 + 3 skipped (0 regression)

Forgot-alert contrast (другие 2 Pa11y errors на /forgot) — Vuetify info-variant
theme, требует design-decision Платон/брендбук; defer в Q.DEFER.002 (A).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:51:55 +03:00
Дмитрий ebebfacab4 docs(audit): Phase 10+11 extra — Pa11y live URLs + TODO sweep
После заказчик'ого «продолжай ещё тут» — два extra прохода вне scope Phase 5/6:

**Phase 10 (Pa11y on live URLs):**
Phase 5 audit запускал Pa11y только на handoff concepts (`liderra_v8_handoff/concepts/v8_*.html`)
из-за конфига `pa11y.config.json`. Live портал не был проверен. Phase 10
закрывает gap на guest URLs:
- /login → 0 issues
- /register → 0 issues
- /forgot → 2 errors WCAG2AA G18 contrast 4.18:1 < 4.5:1 на v-alert rate-limit
- /404 (catch-all) → 1 error WCAG2AA G18 contrast 2.77:1 < 4.5:1 на text-primary link

Auth-required views НЕ verified — требуют session cookie injection в Pa11y CLI.
Q.DEFER.002 в blocked.md с 4 options для следующей сессии.

**Phase 11 (TODO/FIXME sweep):**
Grep `\b(TODO|FIXME|XXX|HACK)\b` over app/**/*.{php,vue,ts}:
- 19 matches in 15 files.
- 6× MVP-defer Б-1 (admin role-guard, saas-admin auth — cross-link Q.HARD.001).
- 8× feature-defer (DashboardView mock data, region_mask decode Plan 6, и т.д.).
- 1× production-readiness (ProcessWebhookJob Sentry::captureException).
- 3× test-infra known квирк 54 (Vuetify teleport в JSDOM).
- 1× false-positive (TwoFactorSetupTest 'totp_secret' string literal).

Все documented in code — tracked work, не surprise.

cspell-words.txt: +Категоризация, +квирки (для audit-docs prose).

Не закрывают P0/P1. Phase 10/11 — informational только.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:47:43 +03:00
Дмитрий 1da23b8253 chore(audit): finalize 2026-05-12 portal full audit
Полный аудит портала проведён в ночь 12.05.2026 на ветке plan5-frontend-projects.
9 phase'ов, 393 findings, 8 fix-commits, 4 BLOCKED-вопроса.

Артефакты:
- docs/superpowers/plans/2026-05-12-portal-full-audit.md — план
- docs/superpowers/audits/*-findings.md — все findings file:line + severity
- docs/superpowers/audits/*-blocked.md — 4 вопроса заказчику
- docs/superpowers/audits/*-report.md — summary с метриками до/после
- audit-screens/views/ — 24 UI smoke screenshots (Playwright)
- audit-screens/legacy/ — 32 untracked PNG из workdir
- app/database/seeders/DemoSeeder.php — idempotent seed
- .gitleaks.toml — allowlist для seeders/audit-docs (демо-фикстуры)
- cspell-words.txt — +12 audit-cited mixed-script artifacts

Метрики (Phase 1+2 baseline → Phase 9 final, все commits 3a8229a..57f0b8e):
- Histoire build BROKEN → 35 stories / 63 variants 
- ESLint 17 → 0 
- vue-tsc 9 → 0 
- Prettier 48 → 0 
- markdownlint 165 → 1 (untracked design.md) 
- cspell 103 → 18 → 0 (after audit-cited words added) 
- Vitest 614 → 614 (0 regression) 
- Pest --parallel 739/0/3 → 739/0/3 
- Vite build 1.80s 0 warnings → 1.72s 0 warnings 
- gitleaks 0 leaks (340 commits) 

🟢 GREEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:37:51 +03:00
Дмитрий 57f0b8e64c docs(claude.md): v1.87 → v1.88 audit-driven sync §0/§2/§6/§8
Phase 6 audit found doc-drift:
- §0/§2/§8 schema метрики застряли на baseline v8.19 (62/12/117/39/5/13/5).
  Dev `liderra` factual after migrate:fresh + partitions:create-months:
  75 root + 102 partition children + 289 indexes + 39 RLS + 5 user funcs
  + 19 triggers + 0 dev roles (5 on prod via db/00_create_roles.sql).
- §0 row «Открытые_вопросы v1.75» → факт v1.77 (Sprint 4 Audit tail
  close, 10.05.2026); note про post-v1.77 deviation (Plan 4/5 + Quiet
  Luxury merged без registry bump).
- §6 «Plan 4 ready for FF-merge» → факт «Plan 4 MERGED в origin/main
  615db99» + новый параграф про Plan 5 frontend + Quiet Luxury + dev-indices
  в `plan5-frontend-projects` (85+ commits ahead на 12.05.2026 night).
- §8 self-review row: baseline ИЛИ dev-actual disambiguation.
- 5 user-функций перечислены поимённо (audit_block_mutation, audit_chain_hash,
  calc_lead_score, report_jobs_log_export, set_pd_subject_request_deadline).

§9 entry для v1.88 описывает полный аудит-сессии: 8 commits Phase 8,
0 регрессий, final baseline Pest 742 / Vitest 614 / Histoire 35 / Vite 1.80s.

Через `/claude-md-management:revise-claude-md` (см. blanket approval
заказчика «исправь всё что сможешь в моё отсутствие»).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:30:14 +03:00
Дмитрий b9038bc3eb chore(routes): add explicit Route::view for /projects, /reminders, /admin/*
Phase 6 audit found inconsistency in routes/web.php SPA-shell list.
Comment (line 188-190) declares «Регистрируем явно, а не catch-all»
for test isolation, but the explicit list missed:
- /reminders, /projects (main views from Plan 5)
- /admin and 7× /admin/* (added in Plans 4 + 5)

These paths worked via Route::fallback (line 211), but that risks
runtime-routes from Pest beforeEach('_test/*') being shadowed by
fallback BEFORE catch-all. Align explicit list with router/index.ts
to honor the documented rationale.

No behavioral change for production (same welcome view returned);
test-suite isolation contract restored.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:25:19 +03:00
Дмитрий cb05657f30 chore(format): prettier --write across 37 .vue/.ts files
Phase 1B audit found 48 files failing `prettier --check`. Auto-apply
via `npx prettier --write resources/js/**/*.{ts,vue,css}` produced
style-only changes:
- consistent quote style
- trailing comma normalization
- spaces around : in v-card style="position: relative" attrs
- explicit ; insertion

No semantic changes. No code-behavior changes. Production-code only;
test files batched separately into `test(frontend):` commit.

Verification:
- npx vitest run → 79/79 files, 614/614 + 3 skipped (no regression).
- npx vue-tsc --noEmit → 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:24:33 +03:00
Дмитрий 6988e80137 chore(cspell): add 79 project + lorem-ipsum + brand-naming + test-fixture words
Phase 1C of audit found 103 unknown words across 12 files (docs/ + web/v8/*.html).
Categorized and added to cspell-words.txt:
- Lorem ipsum (14): handoff placeholder text (amet, consectetur, ... consequat)
- Бренд-нейминг + сторонние сервисы (9): Volna, Vento, Potok, Fraunces, Authy,
  jqlang, FAVOURITE, favourite, potok
- Project terms RU (26): квирк, нормативка, релизный, консьюмер, фичефлаги,
  премиума, медтехом, вайбом, тиловый, слейт, вайб, фиксим, гипотезного,
  капчёй, логируются, синхр, агрегированно, еталонных, задек, диффа,
  закоммичены, перехвачиваться, недозвоном, Неогранич, и т.п.
- Test fixtures + аббревиатуры (28): MRT/VLW/YHC/GVB, lpk/xqz/btv, SMSC,
  LTV, ПАО, НКО, potolki, msk, build-hash fragments (MVZNV, Bjf, DDP, ...),
  funcs, trgm, plpgsql, reestr, sumary
- Фамилии (2 с диакритикой): Бузо́ва, Габбасов

Reduces cspell issues 187 → 18 (90% reduction). Remaining 18 — mixed-script
artifacts + diacritic opechatki в исходниках (web/v8/, audit-docs);
captured как P3 typo-finding'и в audit-blocked.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:24:20 +03:00
Дмитрий 245b76ec43 test(frontend): fix 17 ESLint errors + TwoFactorView router stub
ESLint emitted 17 errors in tests/Frontend/* (production code clean):
- 13× @typescript-eslint/no-explicit-any in axios mock casts
  (BulkActionsBar, ProjectsView, projectsStore specs)
- 3× vitest/no-disabled-tests rule-not-found
  (eslint-plugin-vitest not registered; inline-disable comments stale)
- 1× @typescript-eslint/no-unused-vars on imported beforeEach

Plus Phase 5 audit finding: TwoFactorView.spec.ts test router was
missing /recovery-use stub → Vue Router warn on every TwoFactorView mount.

Changes:
- BulkActionsBar.spec.ts, ProjectsView.spec.ts, projectsStore.spec.ts:
  replace `as any` with `as unknown as ReturnType<typeof vi.fn>` on
  axios method mocks; one case used `as unknown as { regionsOpen: bool }`
  for vm shape.
- NewProjectDialog.spec.ts, ProjectsView.spec.ts: remove stale
  `// eslint-disable-next-line vitest/no-disabled-tests` comments
  (it.skip() lines kept).
- ProjectsView.toolbar.spec.ts: drop unused `beforeEach` from import.
- TwoFactorView.spec.ts: add `/recovery-use` route stub.

Verification:
- npx eslint --max-warnings=0 → exit 0 (was 17 errors).
- npx vitest run on affected specs → 24/27 passed + 3 skipped (was same).
- TwoFactorView spec → 3/3 passed, no Vue Router warn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:23:51 +03:00
Дмитрий 55a9d3fe00 fix(types): unify Project interface + NavItem.countKey + drop legacy Record
vue-tsc was emitting 9 errors from two issues:

1. ProjectCard.vue had a local `interface Project` missing region_mask /
   region_mode / delivery_days_mask, while stores/projectsStore.ts
   exported the canonical one with those fields. ProjectsView.vue passed
   the canonical Project to ProjectCard handler signatures which expected
   the local incomplete one → 5× TS2322.

2. EditProjectDialog passed `project: Project | Record<string, unknown>`
   to NewProjectDialog which expected `Record<string, unknown> | null`.
   Project lacks an index signature → TS2322.

3. AppSidebar.vue template referenced `item.countKey` not declared in
   NavItem interface → 2× TS2339.

Changes:
- ProjectCard.vue: drop local Project, import from projectsStore.
- NewProjectDialog.vue: project prop type → Project | null (was Record).
  Drop `as { id: number }` cast on PATCH URL.
- EditProjectDialog.vue: project prop type → Project | null.
- AppSidebar.vue: add `countKey?: string` to NavItem.
- projectsStore.ts: make region_mask/region_mode/delivery_days_mask
  optional (backward-compat for mock fixtures; production rows always
  populate them by schema).
- Test/story fixtures expanded with delivered_today/is_active/archived_at/
  sync_status to match strict Project shape.

Verification:
- npx vue-tsc --noEmit → 0 errors (was 9).
- npx vitest run on 5 affected specs → 16/16 passed + 2 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:15:26 +03:00
Дмитрий 3a8229a4c7 fix(histoire): register Pinia in setup file + add missing routes
BulkActionsBar.story.vue calls useProjectsStore() in top-level setup,
which executes before story collection. Without Pinia plugin, Histoire
build aborts with `getActivePinia() was called but there was no active
Pinia` — uncaught exception kills the whole build (24 → 0 stories).

Add createPinia() to histoire.setup.ts alongside Vuetify + vue-router.
Also add `/recovery-use` and `/projects` routes to the stub router
(parity with router/index.ts after Plan 5 frontend), so future story
files needing those paths don't emit Vue Router warns.

Histoire build now: exit 0, 35 stories / 63 variants in 80.6s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:15:09 +03:00
Дмитрий b5849bbd2a fix(projects): cyrillic ILIKE via PG ICU + clearable workaround
Корень: dev-БД `liderra` создавалась с LC_CTYPE=C — lower()/upper() не
делает case-folding для кириллицы, `ILIKE '%сп%'` на «Окна СПб» = 0 строк.
Test-БД с Russian_Russia.1251 маскировала проблему.

Системный fix: dev-БД пересоздана через `LOCALE_PROVIDER icu ICU_LOCALE 'und'`
(PG 16+ ICU collation, кросс-платформенно). Точечный COLLATE-workaround не
понадобился — все 5 ILIKE-endpoint'ов теперь работают с кириллицей без
правки кода. CTO-20 закрыт в реестре v1.81; команда CREATE DATABASE с ICU
зафиксирована для prod-deploy.

Сопутствующее:
- ProjectsView clearable: workaround `::after content '✕'` + видимость
  через `.v-field--dirty` (mdi-* font не подключён в проекте — CTO-19
  заведён в реестре).
- LookupsTest: удалён stale case `GET /api/projects?tenant_id=N`,
  заменённый auth:sanctum-роутом в Plan 5.
- Pest +1 регрессионный тест (`search is case-insensitive for Cyrillic`)
  в ProjectsListShowTest, 10/10 / 37 assertions.
- phpstan-baseline регенерирован (3 actingAs + удалённый case).
- cspell-words: +Регистронезависимый, +und.
- app/.backups/ в gitignore.

Verify:
- Pest --parallel: 742 passed / 1 flaky error (CsvReconcileJobTest cache
  race, в изоляции 2/2 PASS) / 3 skipped.
- Browser: «сп» и «окн» возвращают «Окна СПб».

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:25:25 +03:00
Дмитрий 4ee718e668 docs(quiet-luxury): disclose ProjectCard template change bundled in 3fc90f1
Final code reviewer correctly identified that impl-commit 3fc90f1 included
not only the CSS work but also a template change <v-checkbox> →
<label><input><span> on ProjectCard.vue.

Root cause (Task 0 forensics): session-start git status showed
`M app/resources/js/components/projects/ProjectCard.vue` — pre-existing
uncommitted modifications from prior session/work. When the session Read
the file at start, it saw the working-dir version (already with native
<label><input>), not the branch-HEAD 88a13e2 version (with <v-checkbox>).
Stage+commit in 3fc90f1 thus bundled both changes.

The template change is architecturally required for the new CSS to work —
<v-checkbox> renders Vuetify-internal <input> without a sibling <span>,
which is what the scoped :checked + .card-check__box::after selector
needs. The baseline-fix commit 84530d5 was also prepared for the native
input selector, consistent with this template structure.

Updating spec §2 architecture to reflect this honestly rather than leave
a stale «Template / script нетронуты» statement that conflicts with the
diff in main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:14:58 +03:00
Дмитрий 3fc90f12df feat(projects-ui): Quiet Luxury redesign for card-check + 5 dialog v-text-fields
ProjectCard.vue: replace 2px noir solid border on .card-check__box with
1px var(--liderra-line) idle / var(--liderra-line-strong) hover / var(--liderra-teal)
checked. Checked state uses tonal 10% teal bg instead of full fill. Size 20→16px.
Added :focus-visible outline for keyboard nav.

NewProjectDialog.vue: add a local .ld-input-quiet class to all 5 v-text-field
in the dialog (domain / phone / sms keyword / name / daily limit). The class
overrides v-field outline border-color through :deep() to use the tokens.css
1px line / line-strong / teal palette, and sets border-radius to var(--radius-8).
All variant/density/color values come from Vuetify global defaults in
plugins/vuetify.ts:50-54. Includes opacity:1 on every override to neutralize
Vuetify's --v-field-border-opacity 0.38 cascade, plus an explicit error-state
rule with border-color:currentColor to preserve Vuetify's red error border.

Twin elements left out of scope: .toolbar-check__box in ProjectsView.vue,
v-combobox/v-autocomplete/v-btn-toggle inside the same dialog, and the
filter-bar v-select inputs.

Spec: docs/superpowers/specs/2026-05-12-quiet-luxury-elements-1440-896-design.md
Plan: docs/superpowers/plans/2026-05-12-quiet-luxury-elements-1440-896.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:07:20 +03:00
Дмитрий ab47ad250b docs(quiet-luxury): apply reviewer findings on opacity cascade + error state
Task 2 code-quality review (subagent verdict: Ready to merge? No) found
two critical correctness bugs in the original CSS template from spec §3.2:

1. Vuetify's outlined variant collapses sub-element opacity through
   --v-field-border-opacity (= 0.38 at idle). Without explicit opacity:1
   on each override block, --liderra-line (alpha 0.08) effective alpha
   becomes 0.03 → border essentially invisible on ivory backgrounds.

2. Overriding border-color with an explicit value breaks the
   currentColor inheritance Vuetify uses for the error state
   (color: rgb(var(--v-theme-error)) on .v-field--error.v-field__outline).
   Without an explicit error rule that restores currentColor, the red
   error border never appears on any of the 5 validated fields.

Also tightened hover from .ld-input-quiet:hover (which is on the .v-input
root, including hint/error message area) to .v-field:hover inside
:deep() — matches Vuetify's own hover scope and avoids triggering on
helper text hover.

Spec §3.2 and plan Task 2.2 updated to the corrected CSS block with
explicit «almost-trap-avoidance» notes documenting why each adjustment
is needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:52:29 +03:00
Дмитрий 84530d55bf test(projects): fix ProjectCard change-trigger target
Pre-existing failing test from commit c9ee8d8 — data-testid lives on
<label>, but @change handler sits on <input> inside it. jsdom does not
bubble change-event from label to input via @vue/test-utils trigger.
Use child-input selector to fire the event on the right node.

Baseline после fix: 614 passed / 3 skipped / 0 failed (vs 613 / 3 / 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:36:33 +03:00
Дмитрий b2a5a6e18a docs(quiet-luxury): rescope #896 to 5 v-text-fields after vuetify-defaults finding
Task 0 (pre-flight) обнаружил global Vuetify default в plugins/vuetify.ts:50-54
который уже устанавливает variant=outlined density=comfortable color=primary
для всех VTextField. Изначальная гипотеза spec §1.2 «variant=filled по
умолчанию» была неверна — все 5 v-text-field в NewProjectDialog.vue выглядят
одинаково тёмными (Vuetify default border ≈ 60% on-surface), а не «один
filled среди других».

Заказчик принял расширение области: применить .ld-input-quiet ко всем 5
v-text-field (lines 21, 30, 48, 59, 61), убрать неработоспособные явные
props (variant/density/color/rounded — они уже из global default), и
вынести border-radius в :deep(.v-field) override через --radius-8.

Также Task 0 нашёл pre-existing failing test в ProjectCard.spec.ts:43
(change-trigger на <label> вместо <input> внутри); это будет починено
отдельным atomic-коммитом перед Task 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:35:50 +03:00
Дмитрий 50816403bb docs(quiet-luxury): implementation plan for #1440 + #896 redesign
7-task plan (pre-flight + 2 implementation tasks + 3 verification tasks + commit/push)
с TDD-стилем bite-sized шагов: baseline Vitest → CSS-only правка ProjectCard
→ template+style правка NewProjectDialog → full regression → manual smoke →
lefthook + commit + push. Includes verification-before-completion checklist
и rollback план.

Spec ref: docs/superpowers/specs/2026-05-12-quiet-luxury-elements-1440-896-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:34:49 +03:00
Дмитрий 7d86971e9d docs(quiet-luxury): design spec for elements #1440 + #896 redesign
Spec по узкому Quiet Luxury редизайну двух конкретных элементов из
Dev Element Indices: card-check__box в ProjectCard и v-text-field
«Название проекта» в NewProjectDialog. Подход — CSS / prop правки
под существующие tokens.css, без новых primitives.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:21:23 +03:00
Дмитрий 88a13e2001 chore(dev-indices): manifest entries for bulk-actions components 2026-05-12 15:38:11 +03:00
Дмитрий 8f40ea441d feat(projects-bulk): Histoire stories for 3 bulk dialogs 2026-05-12 15:24:24 +03:00
Дмитрий df92ac02ff feat(projects-bulk): wire 3 new dialogs into BulkActionsBar
Add RegionsBulkDialog / DaysBulkDialog / LimitBulkDialog to
BulkActionsBar with open-state refs (regionsOpen/daysOpen/limitOpen),
runBulk helper via store.bulkUpdate, and flex-wrap layout.
Update spec: fix existing tests (bulkAction → bulkUpdate), add 3 new
dialog-wiring tests (7/7 pass; full suite 614+3skipped/0failed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:23:29 +03:00
Дмитрий 4b6ab8f113 feat(projects-bulk): LimitBulkDialog delta or replace mode
Delta mode combines Add/Remove numeric inputs into a single signed delta;
Replace mode switches to an absolute value input via v-checkbox toggle.
5/5 Vitest pass; full suite 611 passed + 3 skipped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:20:18 +03:00
Дмитрий 4c470813b4 feat(projects-bulk): DaysBulkDialog Add/Remove (7 weekday bitmask)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:17:16 +03:00
Дмитрий 3b254fb56f feat(projects-bulk): RegionsBulkDialog Add/Remove (8 ФО bitmask) 2026-05-12 15:14:18 +03:00
Дмитрий 95bba384a1 feat(projects-bulk): select-all toolbar with counter and indeterminate state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:11:21 +03:00
Дмитрий a46e63bdd3 feat(projects-bulk): store selectAllByFilter + bulkUpdate with scope discriminator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:08:08 +03:00
Дмитрий 2d6eb88ce0 feat(projects-bulk): federal districts + weekdays constants for bulk dialogs 2026-05-12 15:04:58 +03:00
Дмитрий cb36a52171 test(projects-bulk): RLS cross-tenant isolation + empty-resolve edge case
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 15:02:04 +03:00
Дмитрий 64d8daede7 feat(projects-bulk): scope.filter resolver + 500-limit guard
Refactor inline scope resolution from ProjectController::bulk() into
ProjectService::resolveBulkScope (BULK_MAX=500 constant). Adds 2 tests:
scope.filter->ids mapping and >500 rejection (12 total, all pass).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:59:59 +03:00
Дмитрий c6eae16282 feat(projects-bulk): update_limit handler with per-project skip on delivered_today conflict
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:55:45 +03:00
Дмитрий c025ec4b69 feat(projects-bulk): update_days handler with bitmask OR/AND-NOT
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:52:23 +03:00
Дмитрий 8220a85a5d feat(projects-bulk): update_regions handler with bitmask OR/AND-NOT
Refactor ProjectService::bulkAction to accept full payload array and
return structured {updated, skipped, warnings}. Add bulkUpdateRegions
using PG raw bitmask expr (region_mask | add) & ~remove & 255.
Add stubs for bulkUpdateDays/bulkUpdateLimit (Tasks 3-4). Update
controller to pass merged payload and return service result directly.
Un-todo Task-1 region validation test; add regions bitmask test (18/20).
Update phpstan-baseline: actingAs count 5->6, restore match.unhandled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:48:10 +03:00
Дмитрий 08f02100fe fix(projects-bulk): treat empty scope.filter as valid scope
Replace !empty() check with has()+is_array() so scope:{filter:{}} is
accepted as "all projects" rather than rejected as missing selection.
Expand scope.filter to IDs in the controller (500-row limit guard) so
the service receives a typed array[]; add Pest coverage for this case.
Update phpstan baseline count for new actingAs() call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:43:45 +03:00
Дмитрий 40202caf34 feat(projects-bulk): extend validation for 6 actions + scope
- BulkProjectActionRequest: add update_regions/update_days/update_limit actions, scope.filter, withValidator for ids-or-scope + delta/replace mutual exclusion
- ProjectBulkActionsTest: 4 new tests (3 pass, 1 todo pending Task 2 service handler)
- ProjectsActionsTest: update > 100 ids limit test to match new max:500
- phpstan-baseline: add 4 actingAs false-positive entries for new test file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:38:59 +03:00
Дмитрий 5c8ad2738a feat(layout): dark topbar + sidebar cleanup + DevIndexBadge moved below
Sidebar: убраны Менеджеры/Напоминания; Работа в порядке
Проекты/Сделки/Канбан/Дашборд; Команда — только Настройки;
снят useRemindersStore (был только под reminders badge).

Topbar: тёмный фон linear-gradient(noir → #04261E) совпадающий
с sidebar #1271; убран breadcrumb «Рабочая область»;
v-toolbar__content padding-left:240 (не уходит под sidebar).

DevIndexBadge: top:64 (ниже топбара, не перекрывает user-chip).

Vitest AppLayout 15/15 PASS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:32:03 +03:00
Дмитрий 4e27db63a3 plan(projects-bulk): implementation plan — 15 tasks TDD 2026-05-12 14:29:35 +03:00
Дмитрий 1d6d1f2671 docs(spec): projects bulk actions — design 2026-05-12 14:23:37 +03:00
Дмитрий 9a7615b257 fix(dev-indices): Esc pause-hover + skip inert Vue compiler tags
#1 (review-Important) — Esc now also calls pauseHover(2000) so the next
mousemove doesn't re-target the cursor element within 16ms. User gets
2 seconds to move off before hover re-engages.

#4 (review-Important) — Plugin walker now skips data-dx injection for
inert Vue compiler tags (template / slot / component / Transition /
TransitionGroup / Suspense / KeepAlive) but still recurses into their
children with the tag preserved in ancestor chain (keeps descendant
signatures stable). Manifest regenerated — no more phantom IDs that
reference no-DOM-element nodes.

Other review findings (CI integration, save-amplification, code-style
polish) skipped: this feature is temporary, will be removed at final
release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 12:32:15 +03:00
Дмитрий e3804cd12b feat(dev-indices): CLI 'npm run dx <id>' for manifest lookup
Prints file:line/tag/text/parent-chain/signature/created for any manifest
entry. Handles deleted IDs (tombstones) with separate message format.
Exit codes: 0=found, 1=not-found-or-no-manifest, 2=usage-error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 12:07:13 +03:00
Дмитрий d238ca5f4a feat(dev-indices): overlay Alt-keys (up/down) + Alt+Shift+I toggle + mini-badges
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 12:05:53 +03:00
Дмитрий d8c33b4cd6 feat(dev-indices): DevIndexOverlay (hover badge + click-copy + Esc + AppShell mount)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 12:02:38 +03:00
Дмитрий 901530ae41 feat(dev-indices): useDevIndices composable (state + DOM walk)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 11:57:35 +03:00
Дмитрий c771192db2 feat(dev-indices): JSON Schema for manifest validation
IDE auto-completion/validation for app/dev-indices.json via the $schema
reference in the manifest header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 11:54:56 +03:00
Дмитрий b182dae89b feat(dev-indices): register plugin in vite.config (dev-only + Vitest guard)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 11:54:13 +03:00
Дмитрий f27ccc0081 feat(dev-indices): Vite plugin core (transform + magic-string injection)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 11:50:15 +03:00
Дмитрий 8edd720395 feat(dev-indices): signature module (structural + data-dev-name escape hatch) 2026-05-12 11:46:20 +03:00
Дмитрий 1f834bfac3 test(dev-indices): cover loadManifest error branches + markDeleted no-op 2026-05-12 11:43:59 +03:00
Дмитрий baf51bd2cf feat(dev-indices): manifest IO module (types + load/save/lookup/tombstones)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 11:37:36 +03:00
Дмитрий 611506faa1 docs(plans): impl plan — dev element indices (10 tasks, TDD-bite-sized)
10 задач с TDD-разбиением: types + manifest IO → signature → Vite plugin core
→ vite.config wiring → JSON Schema → useDevIndices composable → DevIndexOverlay
(hover/click/Esc + App.vue mount) → overlay Alt-keys + Alt+Shift+I toggle → CLI
'npm run dx <id>' → end-to-end smoke. Каждая задача self-contained, кончается
commit'ом.

App.vue mount через defineAsyncComponent + import.meta.env.DEV для надёжного
tree-shake в production. Spec coverage table в конце плана.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 11:31:52 +03:00
Дмитрий f90ddb09c1 docs(specs): design — dev element indices (per-element data-dx + manifest)
Утверждённый дизайн: Vite plugin инжектирует data-dx на каждый element
+ persistent dev-indices.json (commit'ится) + DevIndexOverlay
(hover/Alt-keys/Alt+Shift+I toggle/click-to-copy).

Cтабильность через structural signature (file + ancestor chain + tag +
static attrs + text snippet), tombstones для удалённых ID, escape-hatch
через data-dev-name на важных местах. Production: tree-shake'ится через
import.meta.env.DEV.

+3 слова в cspell-words.txt (реордере/реорден/hmr).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 11:22:30 +03:00
Дмитрий f4ec5dcafa fix(redesign): sidebar position:fixed + main padding-left — restore main content visibility
Hotfix: после Task 12 замены v-navigation-drawer на plain <aside> sidebar остался position:static и толкал v-main в flow ниже (y=901), весь контент уезжал за viewport. Добавлен .ld-sidebar position:fixed top:0 left:0 height:100vh z-index:1006 + .app-main padding-left:232px. Verified via Playwright snapshot — Dashboard KPI/charts отрисованы корректно.
2026-05-12 10:48:15 +03:00
Дмитрий 43250b6773 docs(specs): I2 backlog +5 final-review findings (token leaks + naming + stagger) 2026-05-12 10:33:53 +03:00
Дмитрий 3ce52fc52f docs(specs): Task 18 — portal redesign Iteration 1 acceptance + I2 backlog
Iteration 1 verification sweep (commits 38b985a..e266927):
- Vitest 579 passed / 3 skipped / 0 failed (full suite green)
- ESLint debt 15 errors — all in pre-existing Plan 5 files
  (NewProjectDialog/ProjectsView/projectsStore .spec.ts), 0 touched by redesign
- Type-check errors — all in pre-existing Plan 5 files
  (EditProjectDialog.vue, ProjectsView.vue, NewProjectDialog.spec.ts), 0 touched by redesign
- Histoire build — 4 new stories (StatusPill, Kbd, FilterChip, DensityToggle)
  discovered; build fails on pre-existing BulkActionsBar.story.vue Pinia issue

Acceptance §13 checklist (10 items): 8  / 1  / 1 N/A
- 12 CSS tokens , Inter+JetBrains Mono tnum , AppLayout shell 
- StatusPill in Deals  but  NOT integrated in KanbanCard/DashboardView
- 7 motion patterns + prefers-reduced-motion , Density localStorage 
- Vitest unit tests , Histoire stories 
- Pa11y SPA + Lighthouse N/A (skipped per I1 scope)

§15 new section captures 9 Iteration-2 backlog items: slug reconciliation,
sidebar drawer regression, filter chip stubs, status-legend strip,
KanbanCard hover overlap, sidebar marker regex tightening,
prefers-reduced-motion test for ld-marker-grow, Pa11y SPA config, Lighthouse run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:31:14 +03:00
Дмитрий e2669270f3 feat(redesign): Task 17 — ProjectCard tokens (hover lift + JetBrains Mono numerics) 2026-05-12 10:22:36 +03:00
Дмитрий 22e6bdf8b8 feat(redesign): Task 16 — KanbanView StatusPill + hover lift (motion #4)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:19:21 +03:00
Дмитрий 2f46a3e5ec feat(redesign): Task 15 — DealsView filterbar + density + StatusPill + hover lift (motion #2,#4) 2026-05-12 10:13:19 +03:00
Дмитрий 35662f7b56 feat(redesign): Task 14 — DashboardView KPI count-up (motion #1) + live pulse 2026-05-12 10:06:08 +03:00
Дмитрий a09434eca0 feat(redesign): Task 13 — page transition wiring (Vue Transition + CSS fadeup, motion #6) 2026-05-12 09:59:22 +03:00
Дмитрий 3f956224bd feat(redesign): Task 12 — AppSidebar двухтоновый shell + ⌘K stub + active marker (motion #7) 2026-05-12 09:54:54 +03:00
Дмитрий 2707ff64ab feat(redesign): Task 11 — DensityToggle component (compact/comfortable + persist) 2026-05-12 09:49:37 +03:00
Дмитрий 0b2ec5b802 feat(redesign): Task 10 — FilterChip component (label + count + active states) 2026-05-12 09:47:02 +03:00
Дмитрий 52cc64c9e6 feat(redesign): Task 9 — Kbd component (⌘K, Esc badges; light+dark variants) 2026-05-12 09:45:22 +03:00
Дмитрий ff3bc8bcc1 feat(redesign): Task 8 — StatusPill component + 14-variant Histoire story 2026-05-12 09:43:02 +03:00
Дмитрий 7322c7f33a feat(redesign): Task 7 — useDensity composable (localStorage + rowHeight) 2026-05-12 09:37:43 +03:00
Дмитрий eda13679b4 feat(redesign): Task 6 — useCountUp composable (RAF tween + prefers-reduced-motion) 2026-05-12 09:34:55 +03:00
Дмитрий cdd1b5efdb feat(redesign): Task 5 — useStatusPill composable (14 slugs из db/schema.sql) 2026-05-12 09:29:53 +03:00
Дмитрий ea4570dafe feat(redesign): Task 4 — extend Vuetify theme (12 colors) + global component defaults 2026-05-12 09:27:22 +03:00
Дмитрий b858df569e feat(redesign): Task 3 — motion.css (5 keyframes + reduced-motion wrapper + utilities) 2026-05-12 09:23:50 +03:00
Дмитрий baf27bd02d feat(redesign): Task 2 — typography.css (Inter variable + JetBrains Mono + tnum) 2026-05-12 09:20:13 +03:00
Дмитрий 688d9cfb24 feat(redesign): Task 1 — tokens.css (12 colors + spacing + radii + shadows) 2026-05-12 09:15:29 +03:00
Дмитрий 38b985a473 docs(plans): portal redesign — Quiet Luxury Iteration 1 — 18-task TDD decomposition
Tasks 1-3 CSS foundation (tokens/typography/motion). Task 4 Vuetify theme + global defaults. Tasks 5-7 composables (useStatusPill/useCountUp/useDensity). Tasks 8-11 UI components (StatusPill/Kbd/FilterChip/DensityToggle) + Histoire stories. Task 12 AppSidebar redesign (двухтоновый shell + Cmd-K stub + active marker motion #7). Task 13 page transition wiring (motion #6). Tasks 14-17 view applications (Dashboard count-up #1, Deals filterbar + stagger #2 + hover lift #4, Kanban hover lift, Projects tokens). Task 18 acceptance verification + Pa11y CI sweep.

Self-review: spec coverage complete (all 7 motion patterns wired; stagger #2 added в Task 3 utility + Task 15 application). 0 placeholders. Type consistency across composables verified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 09:09:58 +03:00
Дмитрий 17e07fbe69 docs(specs): portal redesign — Quiet Luxury (Forest extended) design
Approved through superpowers:brainstorming. Direction A (Quiet Luxury) + двухтоновый Pro Console sidebar + 7 motion patterns (count-up, stagger, pill-morph, hover-lift, skeleton, page-transition, sidebar-marker). Forest palette extended до 12 токенов. Inter + JetBrains Mono с tnum. 44px row default + 36px compact toggle. 14 status-pills (точные slugs из db/schema.sql). prefers-reduced-motion обязательный wrapper. Iteration 1 scope: tokens + typography + shell + components defaults + 4 ключевых view (Dashboard, Deals, Kanban, Projects).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:57:42 +03:00
Дмитрий 615db99547 chore(rules): remove R15 motion-runtime restrictions (PSR_v1 v2.0)
Conscious rollback of v1.83 audited construction per user decision
12.05.2026. R15 PSR_v1 section deleted entirely; framer-motion remains
technical block (React-only peerDep), no longer regulatory rule.

Affected:
- PSR_v1 v1.7 -> v2.0 (R15, R0.6 p.11, R8 motion, R11.6, R13 motion rows
  removed; finale + properties reformulated)
- CLAUDE.md v1.87 -> v1.88 (#5 p.12 -> marker; #2 motion stack -> guidance)
- Tooling v1.15 -> v1.16 (#9.2 reformulated; framer-motion + react-spring
  marked as technical block, not regulatory)
- Pravila v1.10 -> v1.11 (#11.5/#13.2 counts updated; #13.9/#13.10 cross-ref
  bumps; #13.10 NOT deleted - it governs R14 UPM/21st pipeline, not R15)
- CHANGELOG_claude_md.md - v1.88 entry

Brainstormed via superpowers:brainstorming. Planned via
superpowers:writing-plans. Executed via superpowers:executing-plans +
/claude-md-management:claude-md-improver + manual Edit.

Spec: docs/superpowers/specs/2026-05-12-remove-r15-motion-restrictions-design.md
Plan: docs/superpowers/plans/2026-05-12-remove-r15-motion-restrictions.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 07:30:57 +03:00
Дмитрий 0fd93fd686 docs(spec+plan): R15 motion-runtime removal — design + impl plan
Brainstormed via superpowers:brainstorming. User decision 12.05.2026:
remove R15 PSR_v1 section entirely (variant B). Conscious rollback of
audited construction from v1.83 (10.05.2026).

Spec: docs/superpowers/specs/2026-05-12-remove-r15-motion-restrictions-design.md
Plan: docs/superpowers/plans/2026-05-12-remove-r15-motion-restrictions.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 07:17:19 +03:00
Дмитрий 0245f12b51 chore(dev): inject DevIndexBadge for visual feature feedback on localhost 2026-05-12 04:45:18 +03:00
Дмитрий 76b1562593 feat(frontend): Plan 5 Task 11 — polling integration (setTimeout-recursion + backoff) 2026-05-11 19:44:56 +03:00
Дмитрий 1c3989a6df feat(frontend): Plan 5 Task 10 — EditProjectDialog wrapper + BulkActionsBar + 7 tests 2026-05-11 19:41:53 +03:00
Дмитрий 92082606e3 feat(frontend): Plan 5 Task 8 — ProjectsView + projectsStore (no polling) + 9 tests 2026-05-11 19:38:59 +03:00
Дмитрий 8bc7838f0c feat(frontend): Plan 5 Task 9 — NewProjectDialog (3 tabs Site/Call/SMS) + story 2026-05-11 19:31:26 +03:00
Дмитрий c9ee8d866e feat(frontend): Plan 5 Task 7 — router + nav + regions + ProjectCard + story 2026-05-11 19:31:23 +03:00
Дмитрий 458fa0b84d feat(projects): Plan 5 Task 6 — destroy + sync + toggle-active + bulk endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 19:06:07 +03:00
Дмитрий 32135e62d2 docs(spec): roadmap post-Plan 5 birdseye до production launch
Линейная лента Sprint 5 → 6 → 7 → 8 → 9 → soft-launch → public launch.
Учитывает закрытые Sprint 0/Sprint 4 и supplier-линию Plans 1-5.
Birdseye-обзор поверх roadmap-to-production-design.md v1.0.
2026-05-11 19:05:31 +03:00
Дмитрий 6238b8b580 feat(projects): Plan 5 Task 5 — update + UpdateProjectRequest + resync trigger
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 19:00:39 +03:00
Дмитрий 85f8e9e7a0 feat(jobs): Plan 5 Task 4 — SyncSupplierProjectJob full impl + ensureSupplierProject
- SyncSupplierProjectJob: replace stub with full implementation
  (tries=3, backoff=[15,60,300]s; resolvePlatforms uppercase B1/B2/B3;
  buildUniqueKey site/call→signal_identifier, sms B2→sender+keyword, B3→sender;
  column name via strtolower($platform) to match schema snake_case)
- SupplierPortalClient: drop final modifier (Mockery testability);
  add ensureSupplierProject() idempotent lookup-or-create wrapper
- Tests: 6 passing (site/call/sms-with-kw/sms-no-kw/exception/partial-failure);
  DI fix via dispatchJobSync() helper resolving mock from container;
  uppercase platform fixtures matching CHECK constraint B1/B2/B3;
  last_error column absent from schema — partial-failure test uses sync_status only
- phpstan-baseline.neon: add $this->mock() Pest TestCase inference gaps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 18:52:51 +03:00
Дмитрий 51019c5aee docs(plan5): rectify region_mode values 'all'/'whitelist'/'blacklist' → 'include'/'exclude'
Schema CHECK constraint on projects.region_mode accepts только 'include'/'exclude'.
Spec/plan изначально использовали 'all'/'whitelist'/'blacklist' (semantic naming),
что не соответствует БД-схеме. При имплементации Task 3 implementer выбрал
'include'/'exclude' (match schema = source of truth). Propagate-fix:

- plan (2 PHP Rule::in + ~10 payload mentions + 4 TS form defaults)
- spec (§4.2 описание, 3 JSON API examples, §6.4 текст, §7.1 StoreProjectRequest)

Чтобы Task 5+ (UpdateProjectRequest, frontend tasks 7-11) не повторили
плановую ошибку.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:40:41 +03:00
Дмитрий 2ffbb49faa fix(projects): Plan 5 Task 3 code-review fixes (2 Important + 2 Minor)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 18:38:52 +03:00
Дмитрий 9d2e7270de feat(projects): Plan 5 Task 3 — store + StoreProjectRequest + ProjectService::create
- StoreProjectRequest: 3-way conditional validation (site domain regex, call 7\d{10}, sms senders required)
- ProjectService::create(): max_projects limit check via Tenant.limits JSONB + dispatch SyncSupplierProjectJob
- ProjectController: constructor DI + store() method returning 201
- SyncSupplierProjectJob: stub (Task 4 полная реализация)
- POST /api/projects route inside auth:sanctum+tenant group (name projects.store)
- Migration add_limits_to_tenants: JSONB DEFAULT '{}' per-tenant limits column
- Tenant model: limits added to fillable + casts as array
- schema.sql/CHANGELOG: tenants.limits documented in v8.20
- phpstan-baseline: +8 actingAs entries for new test file
- Quirk: region_mode in request uses 'include'/'exclude' (schema CHECK) not 'all'/'whitelist' (plan spec typo)
- Quirk: Project::first() → Project::where('signal_identifier','x.ru')->latest()->first() (no RefreshDatabase, persistent test DB)
- 8/8 ProjectsStoreTest passed; 699/706 total (4 pre-existing failures unchanged)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:29:54 +03:00
Дмитрий e242e7d7fc fix(projects): Plan 5 Task 2 code-review fixes (2 Important + 2 Minor)
I-1/M-1: introduce resolvedSupplierProjects() private helper on Project
model; rewrite aggregateSyncStatus(), aggregateLastSyncedAt(),
getSupplierLinks() to read from eager-loaded supplierB1/B2/B3 relations
instead of SupplierProject::find() — eliminates up to 120 SELECTs/page.

I-2: aggregateLastSyncedAt() now uses sortBy(timestamp) instead of
Collection::min() on Carbon objects (string-comparison was unreliable).

M-2: add explanatory comment on intval+array_filter silent-drop behaviour
in the ?ids batch-fetch path.

M-3: new test — ?ids batch silently excludes foreign-tenant project IDs.
M-4: new test — show returns 200 for archived project (read preserved).

PHPStan baseline updated: 2 new test functions raise actingAs() count 7→9.
Tests: 9/9 passed (33 assertions). Larastan: 0 errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 18:15:36 +03:00
Дмитрий 35310b5517 feat(projects): Plan 5 Task 2 — index expanded (filters/search/pagination/ids) + show
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 18:08:01 +03:00
Дмитрий 622773f929 fix(db): Plan 5 Task 1 code-review fixes (2 Important + 2 Minor)
I-1: scopeActive docblock — явное предупреждение что scope НЕ фильтрует
     is_active; приостановленные проекты попадают; пример комбинирования.

I-2: migration down() — комментарий об асимметрии с up() и риске drift
     с schema.sql v8.20 при случайном rollback.

M-1: archived_at перемещён в $fillable на позицию сразу после is_active
     (lifecycle-state рядом с lifecycle-state, как указано в плане).

M-2: CHANGELOG header счётчик восемнадцать → девятнадцать записей.

Tests: ArchivedAtTest 2/2 PASS (4 assertions, 472 ms). No behavior change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 17:51:33 +03:00
Дмитрий 144d4cbb98 feat(db): Plan 5 Task 1 — schema delta v8.19 → v8.20 + Project.archived_at
Schema delta (1 правка в db/schema.sql):
- projects + archived_at TIMESTAMPTZ NULL — soft archive flow (отличие от
  is_active=false который = pause).

Метрики: 62 базовых таблицы / 117 индексов / 39 RLS (без изменений).

Сопутствующие правки:
- db/CHANGELOG_schema.md — v8.20 entry.
- app/Models/Project — fillable+casts: archived_at datetime + scopeActive +
  scopeArchived (whereNull/whereNotNull archived_at).
- Migration guard: Schema::hasColumn() проверка перед ALTER TABLE — предотвращает
  "duplicate column" после migrate:fresh (schema.sql v8.20 уже содержит колонку).

Tests:
- ArchivedAtTest.php — 2 it() блоков: archived_at колонка timestamptz + fillable/casts.
- pest --filter=ArchivedAtTest: 2/2 PASS (4 assertions, 485 ms).
- Full suite: 689/686+3 skipped/0 failed (2094 assertions, 84638 ms).

Quirk зафиксирован: Schema::getColumnType('projects', 'archived_at') → 'timestamptz'
(не 'timestamp') — PostgreSQL TIMESTAMPTZ → Doctrine/Laravel native type string.
План spec ожидал 'timestamp', скорректировано в тесте с комментарием.

Spec: docs/superpowers/specs/2026-05-10-claude-brain-extraction-design.md (Plan 5).
Plan: docs/superpowers/plans/2026-05-10-claude-brain-extraction.md Task 1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 17:39:46 +03:00
Дмитрий 48f27b41e5 docs(plans): Plan 5 (Frontend Projects UI + Backend CRUD) — 12 Tasks TDD decomposition
12 vertical-slice tasks по spec'у 1ca4378: schema delta v8.20 (archived_at),
backend ProjectController CRUD (index/show/store/update/destroy/sync/toggle/bulk),
SyncSupplierProjectJob + 3 FormRequest + ProjectService, frontend ProjectsView
с карточками+прогресс-баром, NewProjectDialog с 3 табами (Site/Call/SMS),
EditProjectDialog wrapper, BulkActionsBar, polling integration через
setTimeout-recursion + exponential backoff.

Через superpowers:writing-plans skill. Self-review: spec coverage 22/22 AC,
2 явно отмеченных TODO (regions data + region_mask 32-bit лимит → OPEN-Plan5-04),
type consistency проверена.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:18:36 +03:00
Дмитрий 1ca4378d14 docs(specs): Plan 5 (Frontend Projects UI + Backend CRUD) implementation design
Spec для full-stack плана: backend CRUD на projects (POST/PATCH/DELETE/sync/bulk),
frontend ProjectsView с карточками+прогресс-баром, NewProjectDialog с 3 табами
(Site/Call/SMS), polling sync-статуса через setTimeout-recursion + backoff,
schema delta v8.19→v8.20 (projects.archived_at).

Через superpowers:brainstorming skill. 11-13 task'ов по vertical-slice TDD
(паттерн Plan 4). Self-review прошёл — 4 inline-фиксы внесены.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:04:07 +03:00
Дмитрий 4bc488e940 fix(admin): AdminPricingTiersView strip ISO-suffix from effective_from caption
Caption "(с 1970-01-01T00:00:00.000000Z)" → "(с 1970-01-01)".
Slice on optional-chain in template; UI smoke verified via Playwright,
Vitest tests/Frontend/AdminPricingTiersView.spec.ts 5/5 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:37:59 +03:00
Дмитрий 8681040479 docs: Plan 4 closure — CLAUDE.md v1.87 + Открытые_вопросы v1.78
CLAUDE.md v1.86 → v1.87:
- §0 Источник истины row «Схема БД»: schema v8.11 → v8.19, 56 → 62 базовых
  таблиц, 97 → 117 индексов, 38 → 39 RLS-политик.
- §2 Стек строка БД: 4 → 5 ролей БД (+crm_supplier_worker BYPASSRLS из Plan 3
  для sharing-flow + Plan 4 ResetMonthlyCountersCommand + CsvReconcileJob).
- §6 Текущая фаза: +Plan 4 closure block (15 коммитов на plan4-billing,
  Pest 687/684+3/0, Vitest 49/428, Histoire 24/31, lychee 0, gitleaks 0).
- §8 Self-review триггеры: метрики обновлены до v8.19 = 62/12/117/39/5/13.
- §9 История версий: +v1.87 entry с накопленным drift'ом от Plans 1+2+3+4.

Через /claude-md-management:revise-claude-md (project rule §5 п.10).

Открытые_вопросы v1.77 → v1.78:
- +Раздел 13 «Plan 4 — 7 новых открытых вопросов» (Биз-25..31).
- Биз-25 P1: дефолтные tier-цены (placeholder в PricingTierSeeder).
- Биз-26 P2: rate-limit 1/час/tenant ZeroBalancePausedMail.
- Биз-27 P2: tenant видит ВСЕ ступени (transparent).
- Биз-28 P1: CSV-схема discovery (BLOCKED Plan 3 Tasks 1-2).
- Биз-29 P3: CSV window 25h.
- Биз-30 P3: Drift threshold 5%.
- Биз-31 P2: pricing-tier повышение цены — единая логика effective 1-е след. мес.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:34:44 +03:00
Дмитрий fded2ee392 chore(lychee): Plan 4 plan-file fix 4 broken paths
Plan-файл лежит в docs/superpowers/plans/, поэтому относительный путь
../../docs/Открытые_вопросы_v8_3.md резолвится в docs/docs/... (двойной docs).
Корректный путь — ../../Открытые_вопросы_v8_3.md (мы уже в docs/).

+ escape line 4536 (placeholder `(path)` в example-template) как code block,
чтобы lychee не трактовал как реальную ссылку.

CV gate Step 1: lychee 298/228 OK/0 Errors/70 Excluded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:59:35 +03:00
Дмитрий 174dbae808 feat(billing): Plan 4 Task 11 — TenantChargesController + ChargesTab + CSV export
Backend TenantChargesController:
- GET /api/billing/charges — paginated list, filters period (current_month / last_month / 90d) + charge_source.
- POST /api/billing/charges/export — StreamedResponse CSV (BOM + UTF-8) с chunkById(500).
- auth:sanctum + tenant middleware — RLS изолирует tenant_id.
- 6 Pest integration tests (RLS isolation + filters + pagination + CSV export).

Frontend ChargesTab.vue:
- v-data-table-server с paginated load + period/charge_source filters.
- CSV-download через blob → createObjectURL.
- Forest-palette + JetBrains Mono tnum.

BillingView.vue — добавлен tab «Списания» с импортом ChargesTab.
ChargesTab.story.vue + 4 Vitest tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:51:13 +03:00
Дмитрий 0f820c4569 feat(admin): Plan 4 Task 10 — AdminSuppliersController + AdminSupplierPricesView (B1/B2/B3 cost editor)
Backend AdminSuppliersController:
- GET /api/admin/suppliers — все 3 поставщика (B1/B2/B3).
- PATCH /api/admin/suppliers/{id} — обновляет cost_rub / quality_score / is_active.
- Validation: cost_rub >= 0, quality_score 0..9.99.
- Audit trail saas_admin_audit_log (stub admin via system-supplier@liderra.local).
- 4 Pest integration tests.

Frontend AdminSupplierPricesView (Vue 3 + Vuetify 3):
- v-data-table 3 строки с inline-editing cost_rub/quality_score/is_active.
- Forest-palette + JetBrains Mono tnum.
- 3 Vitest tests + Histoire story.

Router /admin/supplier-prices route.

Drive-by fix: SupplierProjectFactory.definition() default signal_type
ограничен ['site','call'] — иначе при ->create(['platform' => 'B1']) с
оригинальным random 'sms' нарушается CHECK chk_supplier_projects_b1_not_for_sms
(flaky parallel-pest race condition). Тесты, которым нужен 'sms', продолжают
явно передавать signal_type вместе с B2/B3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:28:03 +03:00
Дмитрий ed5e3f495d feat(admin): Plan 4 Task 9 — AdminPricingTiersController + AdminPricingTiersView (CRUD 7-tier + audit)
Backend AdminPricingTiersController:
- GET /api/admin/pricing-tiers — active + scheduled.
- POST — create 7-tier set с effective_from=DATE_TRUNC('month', NOW()+1 month).
- DELETE /scheduled/{date} — отмена будущей сетки.
- Validation: ровно 7 tier_no 1..7 unique, tier 7 leads_in_tier=null, price>=0.
- Audit trail saas_admin_audit_log на POST + DELETE (через SaasAdminAuditLog
  model: payload_before/after, NOT NULL admin_user_id резолвится через стаб
  system-pricing@liderra.local + ip_address из $request->ip()).
- 8 Pest integration tests.

Frontend AdminPricingTiersView (Vue 3 + Vuetify 3):
- v-data-table активной сетки + scheduled groups + dialog editor.
- Forest-palette + JetBrains Mono для tnum-цифр.
- 5 Vitest unit tests (tests/Frontend/, авто-импорт Vuetify через vite-plugin).
- Histoire story для preview.

Router /admin/pricing-tiers route (layout 'admin', requiresAuth).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:18:01 +03:00
Дмитрий deca81c2d7 feat(supplier): Plan 4 Task 8 — CsvReconcileJob hourly + drift>5% email + supplier_csv_reconcile_log
CsvReconcileJob — hourly резерв-канал приёма лидов через CSV-экспорт поставщика:
- Cache::lock 600s (overlap protection).
- Окно [now-25h, now] (запас 1ч над hourly cron).
- INSERT supplier_csv_reconcile_log status='running' → 'ok' | 'drift_alert' | 'failed'.
- Missing vids → INSERT supplier_leads (platform extracted из project, source='csv_recovery',
  recovered_from_csv_at=now) + dispatch RouteSupplierLeadJob.
- Drift > 5% → CsvDriftAlertMail на services.supplier.alert_email.
- UNIQUE-vid conflict → log + skip (idempotency).
- На SupplierTransientException/любой Throwable → status='failed', error_message, rethrow.

CsvDriftAlertMail + blade-template emails/csv_drift_alert.

routes/console.php — Schedule::job(new CsvReconcileJob)->hourly().
config/services.php — supplier.alert_email default 'ops@liderra.ru'.

6 integration tests (CsvReconcileJobTest) + Schedule registration test (через
Http::fake + Bus::fake + Mail::fake + SharesSupplierPdo trait для cross-connection).

Parallel-test race fix: putSupplierSession() вызывается прямо перед SUT, потому
что Sync/Cleanup tests'ы в afterEach делают forget('supplier:session'), а в
--parallel режиме воркеры делят Redis DB+prefix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:04:49 +03:00
Дмитрий cb86065588 feat(supplier): Plan 4 Task 7 — SupplierCsvParser (streaming) + SupplierPortalClient::downloadLeadsCsv
SupplierCsvParser — pure streaming-generator CSV parser:
- BOM (UTF-8 EF BB BF) + CRLF normalization
- Malformed rows (< 6 columns) skipped + Log::warning
- 5 unit tests: empty / 1 row / 1000 rows / malformed / BOM+CRLF

SupplierPortalClient::downloadLeadsCsv(CarbonInterface, CarbonInterface):
- GET /admin/report/index?type=49 через существующий request() helper
- Наследует auth/retry семантику (401 → RefreshSession, 5xx → Transient, 4xx → Client)
- 3 unit tests через Http::fake: 200 / 401 retry / 500

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:50:11 +03:00
Дмитрий ce87936f44 feat(billing): Plan 4 Task 6 — auto-pause flow + ZeroBalancePausedMail + 1/hour rate-limit
При InsufficientBalanceException в LedgerService::chargeForDelivery:
- DB::transaction откатывается (Deal/charge/balance не тронуты).
- Outer catch в createDealCopyForProject вызывает handleInsufficientBalance:
  * UPDATE projects.is_active=false через pgsql_supplier (BYPASSRLS).
  * Email ZeroBalancePausedMail через NotificationService::notifyZeroBalancePaused.
  * Rate-limit 1/час/tenant через Redis SETNX (Cache::add).
  * Log::warning с tenant_id/project_id/balance details.
- Возвращаем false (не rethrow), чтобы handle()-loop продолжал routing остальным tenant'ам.

5 тестов: project paused / email sent / rate-limit 1/h / 2nd email after 65min /
sharing-flow isolation (A paused, B receives).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:43:51 +03:00
Дмитрий dadfdcaa7e feat(commands): Plan 4 Task 5 — ResetMonthlyCountersCommand + Schedule monthlyOn(1, 00:00) МСК
Месячный cron-сброс tenants.delivered_in_month + projects.delivered_in_month
1-го числа каждого месяца в 00:00 МСК. Идёт через pgsql_supplier BYPASSRLS
connection (паттерн ResetDeliveredTodayCommand). Идемпотентный
(WHERE delivered_in_month <> 0 → повторный запуск 0 affected rows).

4 теста: reset multi-tenant + idempotency + Schedule registration +
BYPASSRLS without SET LOCAL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:28:13 +03:00
Дмитрий e401491947 feat(supplier): Plan 4 Task 4 — integrate LedgerService в RouteSupplierLeadJob + Task 3 carry-overs
Task 4 — integration:
- handle() / createDealCopyForProject() — +5-й параметр LedgerService.
- Заменён старый balance_leads-- + BalanceTransaction блок на
  \$ledger->chargeForDelivery(\$tenant, \$deal, \$lead) с try/catch для
  InsufficientBalanceException (Log::warning + rethrow; auto-pause flow
  в Task 6).
- LeadRouter::matchEligibleProjects — расширен фильтр tenant balance с
  (balance_leads > 0) на (balance_leads > 0 OR balance_rub > 0), чтобы
  rub-only tenant дошёл до LedgerService (single arbiter for dual-balance).
- 4 E2E теста в tests/Feature/Supplier/RouteSupplierLeadJobBillingTest.php:
  prepaid charge + BalanceTransaction (carry-over M-2), rub charge + BT,
  supplier_lead_costs gap-fix (2 deal-копии), retry idempotency.

Plan 4 Task 3 carry-overs (минорные правки по code-review d2030f9):
- I-2: PHPDoc на LedgerService::chargeForDelivery — @throws + @precondition
  (caller wraps в DB::transaction с lockForUpdate Tenant).
- I-4: trim() на raw_payload['project'] в resolveSupplierId (defense
  against whitespace).

Прочие правки:
- tests/Feature/Jobs/RouteSupplierLeadJobTest.php — +PricingTierSeeder
  в beforeEach + +5-й LedgerService параметр в runRouteJob().
- tests/Feature/Integration/SupplierLeadFlowTest.php — +PricingTierSeeder
  в beforeEach (test использует full webhook→job pipeline).
- tests/Feature/Services/LeadRouterTest.php — rename теста про balance_leads
  → \"zero in BOTH balance_leads AND balance_rub\" + новый тест
  \"rub-only tenant ДОЛЖЕН пройти\".
- phpstan-baseline.neon — +5 entries для TestCall::seed() + Tenant/LeadCharge
  property.notFound в новых файлах (IDE helper @mixin re-generation —
  отдельная задача).

Метрики: Pint clean, PHPStan 0 errors, Pest 646/643+3 skipped/0 failed
(21.1s parallel). Plan 4 Task 4 закрыт.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:06:38 +03:00
Дмитрий d2030f9121 feat(billing): Plan 4 Task 3 — LedgerService::chargeForDelivery (dual-balance + lead_charges/supplier_lead_costs INSERT) 2026-05-11 09:42:29 +03:00
Дмитрий 1e0c0ab90a fix(billing): Plan 4 Task 2 code-review fixes (2 Important + 1 Minor)
- PricingTierResolver::resolveForCount — InvalidArgumentException на
  $leadOrdinal < 1 (closes I-1: defensive contract validation).
- PricingTierRepository::activeAt — explicit @var Collection<int,
  PricingTier> annotation для type narrowing (closes I-2; firstOrFail
  отвергнут — Stan ругается на Eloquent\Model return-type).
- PricingTierResolverTest — +1 unit test (8/8 PASS): throws на 0/-1.
- PricingTierRepositoryTest — +1 integration test (5/5 PASS): excludes
  inactive tiers (closes M-2 coverage gap).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:17:13 +03:00
Дмитрий e07d025efd feat(billing): Plan 4 Task 2 — PricingTierResolver + Repository (pure resolver + DB-обёртка)
- PricingTierResolver: pure-function, ищет tier для N-го лида (1-based).
  100→tier1, 101→tier2, 6000→tier6 (cumul.sum 1-6), 6001+→tier7 (NULL=unlimited).
  RuntimeException на пустой коллекции.
- PricingTierRepository::activeAt(Carbon): DB-обёртка, MAX(effective_from) <= $at
  per tier_no (учёт «новая сетка перекрывает старую»), is_active=true.
- 7 unit-тестов (in-memory, без БД) + 4 integration-теста (DatabaseTransactions
  с baseline-cleanup для seed-7-tiers из PricingTierSeeder Task 1).
- phpstan-baseline.neon: +3 entry (Pest TestCall::\$resolver/\$tiers/\$repo) —
  следуем project-convention (см. SupplierResolverTest идентичные baseline-entries).

Spec: docs/superpowers/specs/2026-05-11-plan4-billing-csv-admin-design.md §3.1
Plan: docs/superpowers/plans/2026-05-11-plan4-billing-csv-admin.md Task 2

Tests: 633 passed / 3 skipped / 0 failed (+11 new); pint clean; stan 0 errors above baseline.
2026-05-11 09:05:22 +03:00
Дмитрий 1e3c157603 fix(test): Plan 4 Task 1 regression — PricingTierTest::scopeActive baseline-aware
Task 1 (a907fea) добавил $this->call(PricingTierSeeder::class) в DatabaseSeeder,
который автоматически seed'ит 7 ступеней при migrate:fresh --seed на dev/testing.
Существующий PricingTierTest::scopeActive ожидал empty pricing_tiers до factory,
поэтому ->count() == 1 → 8 fail в isolation (pest tests/Feature/Models/PricingTierTest.php).

Fix: scopeActive теперь считает baseline = PricingTier::active()->count() ДО factory create,
ожидает baseline + 1 после factory (1 активный + past, 1 inactive, 1 active + future).

PricingTierTest::current() — не затронут: keyBy('tier_no') корректно перезаписывает
seed-rows (effective_from='1970-01-01') свежими factory-rows (effective_from=now()->subDay()).

Verified:
- pest tests/Feature/Models/PricingTierTest.php — 4/4 PASS, 12 assertions
- pest --parallel — 619 passed / 3 skipped / 0 failed / 1923 assertions

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:59:06 +03:00
Дмитрий e5ee9dce0d fix(db): Plan 4 Task 1 code-review fixes (4 Important issues)
- chk_lead_charges_prepaid_zero_price moved inline в CREATE TABLE lead_charges
  (consistent с ~30 другими CHECK constraint'ами в schema.sql).
- LeadCharge.casts() — убран no-op 'charge_source' => 'string' (Eloquent
  возвращает VARCHAR как string без cast'а; consistent с SupplierLead.platform).
- SchemaDeltaTest — добавлен uses(DatabaseTransactions::class) для tests 1+2
  (rollback после теста, project convention LeadChargeTest/PricingTierTest).
- SchemaDeltaTest test #5 — замена destructive migrate:fresh на static parse
  count(CREATE TABLE) / count(CREATE INDEX) / count(CREATE POLICY) в schema.sql.
  Устраняет cross-test coupling в sequential pest run; параллельно убирает
  LARAVEL_PARALLEL_TESTING skip — теперь все 5 тестов выполняются в parallel.

Метрики из static parse: 62 base tables / 117 indexes / 39 RLS policies
(совпадают с schema v8.19, spec §2.4).

All 5 SchemaDeltaTest assertions still pass. No new schema changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:51:18 +03:00
Дмитрий a907fea031 feat(db): Plan 4 Task 1 — schema delta v8.18 → v8.19 + models/seeder
Schema delta (4 правки в db/schema.sql):
- tenants + delivered_in_month INT NOT NULL DEFAULT 0 CHECK (>=0) — month
  counter для PricingTierResolver O(1) lookup на горячем пути.
- lead_charges + charge_source VARCHAR(8) DEFAULT 'rub' CHECK IN ('prepaid','rub')
  + ALTER ADD CONSTRAINT chk_lead_charges_prepaid_zero_price (prepaid → price=0).
- supplier_leads + recovered_from_csv_at TIMESTAMPTZ + partial index
  WHERE recovered_from_csv_at IS NOT NULL.
- Новая таблица supplier_csv_reconcile_log (SaaS-level, без RLS) + 2 индекса
  (started_at DESC, partial status WHERE status IN ('drift_alert','failed')).

Метрики: 61 → 62 базовых таблиц / 114 → 117 индексов / 39 RLS (без изменений).

Сопутствующие правки:
- db/CHANGELOG_schema.md — v8.19 entry (18 записей).
- db/02_grants.sql — GRANT SELECT,INSERT,UPDATE on supplier_csv_reconcile_log
  + GRANT USAGE,SELECT on sequence для crm_supplier_worker.
- app/Models/Tenant — fillable+casts: delivered_in_month integer.
- app/Models/LeadCharge — fillable+casts: charge_source string.
- app/Models/SupplierLead — fillable+casts: recovered_from_csv_at datetime.
- LeadChargeFactory — defaults charge_source='rub' + prepaid() state.
- PricingTierSeeder (новый) — 7 ступеней дефолтного тарифа (placeholder,
  Plan 4 Открытый вопрос #1: 100/200/400/800/1500/3000/∞ leads at
  50000/45000/40000/35000/30000/27000/25000 копеек).
- DatabaseSeeder — call PricingTierSeeder; убран broken Laravel scaffold
  User::factory(['name' => ...]) (наша схема first_name/last_name).

Tests (Plan 4 surface):
- SchemaDeltaTest.php — 5 it() блоков: delivered_in_month CHECK, charge_source
  CHECK на prepaid+zero-price, recovered_from_csv_at колонка, reconcile_log
  таблица+status CHECK, migrate:fresh idempotency (skip in parallel).
- pest --filter=SchemaDeltaTest: 5/5 PASS (9 assertions, 2076 ms).
- pest --filter='Tenant|LeadCharge|SupplierLead|Plan4': 111/111 PASS.

CI gates:
- composer pint: passed.
- composer stan: passed (0 errors above baseline — @phpstan-ignore-next-line
  на $this->markTestSkipped в Pest closure rebound context).

Verify:
- migrate:fresh --seed на DB_DATABASE=liderra_testing: 0 errors, 754 ms.
- PricingTier::count() = 7.

Концерны (НЕ блокируют Task 1):
- pest --parallel: 617/622 PASS + 4 skipped + 1 flaky FAIL — flaky test
  колеблется между ProjectExtensionsTest::supplierB1_B2_B3_relations
  (SupplierProjectFactory race: closure пикает signal_type=sms до override
  platform=B1 → CHECK chk_supplier_projects_b1_not_for_sms violation)
  и NewLeadNotificationTest::webhook_дубль_Биз_19 (известный microsecond
  precision quirk в anti-spam exclusion, memory feedback_environment.md).
  Оба теста существуют на main (HEAD 0802f7c = plan4-billing branch HEAD
  до этого commit'а), Plan 4 их не трогает. Фиксы — вне Task 1 scope.

Spec: docs/superpowers/specs/2026-05-11-plan4-billing-csv-admin-design.md §2.
Plan: docs/superpowers/plans/2026-05-11-plan4-billing-csv-admin-plan.md Task 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:38:38 +03:00
Дмитрий 0802f7cf4c docs(plans): Plan 4 (Billing + CSV Reconcile + Admin) — 12 Tasks TDD decomposition
writing-plans skill output для Plan 4 spec (commit 901cf98). 12 атомарных
Task'ов в 3 фазах:

Фаза I — Billing core (Tasks 1-4):
  1. Schema delta v8.18 → v8.19 (+1 таблица supplier_csv_reconcile_log,
     +3 колонки, +3 индекса, +2 CHECK) + Eloquent fillable/casts +
     PricingTierSeeder с 7 дефолтными ступенями.
  2. PricingTierResolver (pure resolver) + PricingTierRepository (DB-обёртка
     для активной сетки) с 7 + 4 тестами.
  3. LedgerService::chargeForDelivery — dual-balance (prepaid-first + bcmath
     rub) + lead_charges/balance_transactions/supplier_lead_costs INSERT с
     6 integration тестами.
  4. Integration LedgerService в RouteSupplierLeadJob с 4 E2E sharing-flow
     тестами + retry-idempotency.

Фаза II — Operations (Tasks 5-8):
  5. ResetMonthlyCountersCommand + Schedule monthlyOn(1, '00:00') Europe/Moscow
     через pgsql_supplier BYPASSRLS, 4 теста.
  6. Auto-pause flow + ZeroBalancePausedMail + 1/час/tenant rate-limit через
     Redis SETNX, 5 sharing-isolation тестов.
  7. SupplierCsvParser (streaming generator) + SupplierPortalClient::downloadLeadsCsv
     с 5 + 3 unit/Http::fake тестами.
  8. CsvReconcileJob (hourly + 25h окно + drift > 5% email) + CsvDriftAlertMail
     + Schedule entry с 6 integration тестами.

Фаза III — UI (Tasks 9-12):
  9. AdminPricingTiersController + AdminPricingTiersView (7-tier CRUD + audit)
     + 4 Histoire variants + 5 Vitest тестов.
  10. AdminSuppliersController + AdminSupplierPricesView (B1/B2/B3 cost editor)
      + 2 Histoire + 3 Vitest.
  11. TenantChargesController + ChargesTab в BillingView + CSV export через
      StreamedResponse + 3 Histoire + 4 Vitest.
  12. Verification gate (14-step CV) + 7 новых Биз-вопросов в реестр +
      CLAUDE.md/memory bumps + FF-merge.

Каждый Task имеет TDD-цикл (failing test → run FAIL → impl → run PASS →
pint + stan + parallel pest → commit) с exact commands и полным кодом
(нет placeholders). Self-review против spec — все 7 разделов покрыты.

+1 термин в cspell-words.txt (bcmath) для прохождения lefthook cspell stage.

Inherits from: Plan 1+2+2.5+2.6+3 (HEAD origin/main = 901cf98 spec).
Parent spec: docs/superpowers/specs/2026-05-11-plan4-billing-csv-admin-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:10:51 +03:00
Дмитрий 901cf98281 docs(specs): Plan 4 (Billing + CSV Reconcile + Admin) implementation design
Brainstorming output для Plan 4: активация ступенчатого биллинга
(pricing_tiers/lead_charges никем не читались/писались), резервный CSV-канал
приёма лидов из портала поставщика, Admin UI (pricing-tiers editor +
supplier-prices editor + tenant ChargesTab).

9 разделов: контекст + 8 бизнес-инвариантов + 9 out-of-scope; schema delta
v8.18 → v8.19 (+1 таблица supplier_csv_reconcile_log, +3 колонки
tenants.delivered_in_month/lead_charges.charge_source/supplier_leads.recovered_from_csv_at,
+3 индекса, +2 CHECK); billing flow в RouteSupplierLeadJob (3 новых сервиса
PricingTierResolver/LedgerService/InsufficientBalanceException + dual-balance
prepaid-first логика + bcmath денежная арифметика); monthly reset
(ResetMonthlyCountersCommand + Schedule monthlyOn(1,'00:00') Europe/Moscow);
auto-pause flow (project.is_active=false + email с rate-limit 1/час/tenant);
CSV reconcile (расширение SupplierPortalClient + SupplierCsvParser +
CsvReconcileJob hourly + drift > 5% → email); Admin UI (2 SaaS-admin
view + 1 tab в существующем BillingView); тестовая стратегия (+71 теста)
и 10 AC; ограничения (6 неверифицированных).

Закрывает TODO «Биллинг per Plan 4» в RouteSupplierLeadJob.php:48.
Inherits from Plans 1+2+2.5+2.6+3 (HEAD origin/main = 926fee9).

Self-review applied 5 fixes inline: AC ссылки §3.5 → §7.1; auto-pause UPDATE
через pgsql_supplier BYPASSRLS connection; supplier_id source для
supplier_lead_costs INSERT; bccomp(bcmul()) вместо PHP float compare;
GRANT-policy в db/02_grants.sql, не schema.sql.

7 открытых вопросов с дефолтами в §7.6 для последующего ввода в реестр.

+5 терминов в cspell-words.txt (декрементится / Инкрементится / Подписочный
/ bcdiv / TRUNC) для прохождения lefthook cspell stage.

Парный план (writing-plans output) будет в docs/superpowers/plans/ отдельным
коммитом после согласования spec'а.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 07:56:22 +03:00
Дмитрий 926fee9435 chore(lychee): exclude Plan 3 spec+plan files (project-root-relative paths)
Plan 3 spec и plan markdown files (commits 1a265b5 + 989256b) используют
project-root-relative ссылки на проектные файлы (app/, db/, lefthook.yml).
GitHub UI рендерит их корректно; lychee local resolution через relative
fallback к markdown file directory ломается с 33 'file not found' errors.

Альтернативы:
- Переписать ~30 ссылок на ../../../ prefixes — overhead высокий
- Fix lychee --base/--root-dir не работает на multi-file batch с разной
  глубиной vlozenii (docs/superpowers/specs/ vs docs/)

Это hygiene fix перед push merged main; pre-push lychee теперь pass.
Links валидируются вручную при review parent spec/plan.

Cause: pre-push lychee на git push origin main после merge supplier-sync-plan3.
2026-05-11 07:02:20 +03:00
Дмитрий 734b0ab5db merge: Plan 3 (Supplier Sync) — Tasks 3-9 + final review C-1+I-2 fixes
10 commits на branch supplier-sync-plan3:
- 6d6181b Task 3 — switch supplier-flow на pgsql_supplier (BYPASSRLS)
- 8c70255 Task 3 fix — 4 Important + 3 Minor
- 8fc9d3e Task 4 — SupplierPortalClient HTTP-обёртка
- a8a23cb Task 4 fix — 4 Important defense-in-depth
- f298984 Task 5 — RefreshSupplierSessionJob + PlaywrightBridge
- dedaae5 Task 6 — SyncSupplierProjectsJob + SupplierQuotaAllocator
- c685985 Task 7 — CleanupInactiveSupplierProjectsJob Phase A→B→C
- ecb6314 Task 8 — RetryFailedSupplierJobsCommand + 5 Schedule entries
- 8a611eb Task 9 — E2E mock-server skeleton (Linux CI pending)
- c52d42d Final review C-1+I-2 fixes (test load + lock TTL)

Closed backlog (Plan 2.6 #iv direction):
- BLOCKER #6: failed_webhook_jobs RLS NULL tenant under BYPASSRLS
- WARN #2: LeadRouter под crm_supplier_worker видит все tenants
- WARN #3: ResetDeliveredTodayCommand аналогично

Spec §7 acceptance: 9/10  (#4 session refresh smoke deferred — Task 1 BLOCKED).

Метрики merge'а:
- Pest parallel: 617/614+3 skipped/0 failed (1910 assertions)
- PHPStan 0 errors, Pint clean, gitleaks 0 leaks
- 0 schema changes (paritет с design §2.3)
- 6 supplier Schedule entries registered (artisan schedule:list)

Defer-to-post-merge (per final reviewer):
- Tasks 1+2 BLOCKED на credentials поставщика
- I-1 onOneServer (multi-server scaling gated на Б-1)
- I-3 save_orphan recovery (rare DB write fail после HTTP success)
- I-4 region_mode='exclude' regions_reverse passthrough
- I-5 #4 acceptance criterion deferred

Design: docs/superpowers/specs/2026-05-11-plan3-supplier-sync-design.md (1a265b5)
Plan: docs/superpowers/plans/2026-05-11-supplier-sync-plan3.md (989256b)
2026-05-11 06:55:29 +03:00
Дмитрий 896565087d chore: declare brain v1.0 installed
Marker file for the new Claude Brain repository at c:/моя/проекты/claude-brain/.
Brain artifacts (CLAUDE.md / Pravila / Tooling / Plugin_stack_rules / hooks /
settings / plugin manifest / MCP templates) are now versioned independently
and synced into this project via:

  cd c:/моя/проекты/claude-brain
  ./scripts/install.sh --target=<this-repo> --version=brain-vX.Y

Future edits to brain artifacts: edit in claude-brain repo, then re-run install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 06:45:32 +03:00
720 changed files with 162690 additions and 2213 deletions
+2
View File
@@ -0,0 +1,2 @@
brain-v1.0
sha: 52584df34e1a8b2117b3f254731b06f52d84645c
+146
View File
@@ -0,0 +1,146 @@
<!-- adr-kit-guide v0.13.0 -->
<!-- Canonical project-side ADR guide. Copied from the plugin's templates/adr-kit-guide.md to .claude/adr-kit-guide.md by /adr-kit:init, /adr-kit:upgrade, and /adr-kit:setup. -->
<!-- This file is plain markdown — readable by Claude Code, headless `claude -p`, shell scripts in pre-commit hooks, evaluator scripts, and any agent that doesn't process @-imports. Do not embed Claude-Code-specific syntax inside this file. -->
# ADR Kit Guide
This project uses [adr-kit](https://github.com/rvdbreemen/adr-kit) to manage Architecture Decision Records. The kit ships:
- a project-side guide (this file) referenced from `CLAUDE.md`,
- a library of slash commands and a subagent for ADR authorship,
- a pre-commit hook that catches code changes drifting outside accepted ADRs.
ADR files live at `docs/adr/ADR-NNN-kebab-case-title.md`. They are versioned, immutable once accepted, and the durable record of *why* the codebase looks the way it does.
## Three operating modes
| Mode | When | Entry point |
|---|---|---|
| **Init / bootstrap** | Once per project: scan source + docs, propose a starter ADR set, hook the kit into `CLAUDE.md`, install the pre-commit hook | `/adr-kit:init` |
| **Per-commit verification** | Every `git commit`: declarative-rule check **plus** Claude Sonnet LLM judge for `llm_judge: true` ADRs in one batched call. Default-on as of v0.13.0. Falls back to declarative-only when the `claude` CLI is unavailable | `.githooks/pre-commit` (auto) |
| **On-demand invocation** | Mid-session: write a new ADR, judge a staged diff, supersede an existing decision | `/adr-kit:adr`, `/adr-kit:judge`, `adr-generator` subagent |
## Slash commands
| Command | Purpose | User-only? |
|---|---|---|
| `/adr-kit:init` | One-shot project bootstrap (audit codebase, generate ADRs, install hook). Combines `setup` + audit + `install-hooks`. | yes |
| `/adr-kit:adr` | Author a single ADR (delegates to `adr-generator` subagent; runs four verification gates). | no — model can self-call |
| `/adr-kit:judge` | Interactive judge against a staged diff. Runs declarative checks + in-session LLM check for `llm_judge: true` ADRs. Walks resolution paths on violation. | no — model can self-call |
| `/adr-kit:lint` | Validate existing ADRs against the four verification gates. | yes |
| `/adr-kit:migrate` | Rewrite legacy ADRs into canonical format. | yes |
| `/adr-kit:setup` | Append `## ADR Kit` block to `CLAUDE.md` (idempotent). | yes |
| `/adr-kit:upgrade` | Migrate v0.11 → v0.12 footprint without re-running the heavy audit. | yes |
| `/adr-kit:install-hooks` | Install or uninstall the pre-commit hook. | yes |
## The four verification gates
An ADR cannot move from `Proposed` to `Accepted` until all four pass.
1. **Completeness** — every required section is present and non-empty: Status, Context, Decision, Alternatives Considered (≥ 2), Consequences (positive + negative), Related Decisions, References. Plus filename matches `ADR-NNN-kebab-case.md` and the heading number agrees.
2. **Evidence** — Context or References cites at least one concrete external/internal artefact (incident, profiling data, code path, RFC, vendor doc). No hand-waving justifications.
3. **Clarity** — Decision section names a single concrete choice (not a survey), uses imperative voice, no hedging language ("perhaps", "we should consider"). Identifiers (file paths, function names, config keys) are traceable.
4. **Consistency** — filename, heading number, and any cross-references resolve. No duplicate ADR numbers in the directory.
`bin/adr-lint` enforces Completeness and Consistency deterministically. Evidence and Clarity are heuristic; opt in via `--gates evidence,clarity` or run `/adr-kit:lint` to use the LLM-aware skill.
## Authoring workflow (`/adr-kit:adr` or `adr-generator`)
1. Identify the architecturally significant change (architecture, NFRs, interfaces, dependencies, build/CI tooling). Refactors and bug fixes within existing patterns do NOT need an ADR.
2. Invoke `/adr-kit:adr` (or the `adr-generator` subagent). Provide: title, context with concrete forces, ≥ 2 alternatives with rejection reasons, consequences (both directions), related ADRs.
3. The agent applies the four gates and writes `docs/adr/ADR-NNN-…md` with `Status: Proposed`.
4. Human review. Iterate until all gates pass.
5. Flip Status to `Accepted, YYYY-MM-DD` after explicit human approval. **Never self-approve.**
6. If the decision touches code in a mechanically expressible way, add an `Enforcement` block (see below) so the pre-commit hook can guard the boundary.
## Enforcement block (v0.12+)
Optional `## Enforcement` section at the end of an ADR. Fenced JSON code block, parsed by `bin/adr-judge`. Schema: plugin's `schemas/adr-enforcement.schema.json`.
```json
{
"forbid_pattern": [
{ "pattern": "\\bArduinoJson\\b", "path_glob": "src/**/*.{ino,cpp,h}",
"message": "Use snprintf_P + sendJsonMapEntry; ArduinoJson fragments the heap (ADR-042)." }
],
"forbid_import": [
{ "pattern": "^#include\\s+<ArduinoJson\\.h>", "path_glob": "src/**" }
],
"require_pattern": [],
"llm_judge": false
}
```
**Rules:**
- `forbid_pattern` — regex must NOT match any added line in the diff (lines starting with `+`, excluding `+++` markers).
- `forbid_import` — same engine as `forbid_pattern`; the separate name documents intent.
- `require_pattern` — regex must match at least once in the post-diff content of any file matching `path_glob`.
- `llm_judge: true` — Claude Sonnet evaluates the diff against this ADR's `## Decision` text at commit time (default-on as of v0.13.0). The pre-commit hook batches all `llm_judge: true` ADRs into one Sonnet call and blocks the commit on `VIOLATION`. Falls back gracefully (advisory only, exit 0) when the `claude` CLI is missing.
- ADRs with no Enforcement block are skipped silently by the judge.
**Path globs** support `**` (recursive). Examples: `src/**/*.py`, `tests/**`, `**/Makefile`.
## Pre-commit hook
After `/adr-kit:init` (or `/adr-kit:install-hooks`), every `git commit` runs `bin/adr-judge` on the staged diff with two passes:
- **Declarative pass** — fast, regex-only, no LLM. A violation exits non-zero and blocks the commit.
- **LLM pass (Sonnet, default-on as of v0.13.0)** — all `llm_judge: true` ADRs are batched into one `claude -p --model claude-sonnet-4-6` call. Sonnet returns a per-ADR JSON verdict; any `VIOLATION` blocks the commit with the model's one-sentence reason. Falls back gracefully when the `claude` CLI is missing or unauthenticated — never blocks a legitimate commit due to tooling drift.
**Cost shape** (typical project, 50 `llm_judge` ADRs, small diff): roughly $0.100.30 per commit on Sonnet 4.6 with prompt caching. Latency 510s. Configurable via `judge.llm_model` / `judge.llm_timeout_seconds` / `judge.llm_cmd` in `docs/adr/.adr-kit.json`.
**Knobs:**
- Disable LLM pass per commit: `ADR_KIT_NO_LLM=1 git commit -m "…"`
- Disable hook entirely per commit: `ADR_KIT_HOOK_DISABLE=1 git commit -m "…"`
- Switch model: set `judge.llm_model: "claude-haiku-4-5"` in `.adr-kit.json` for higher throughput at lower cost.
- Remove permanently: `/adr-kit:install-hooks --uninstall`
## Supersession (changing a decision)
Accepted ADRs are immutable. To change a decision:
1. Author a new ADR with the next number. Status `Proposed`. The Decision should explain what changes and why now.
2. In its Related Decisions: `Supersedes ADR-OLD`.
3. After the new ADR is `Accepted`: edit ONLY the old ADR's Status line to `Superseded by ADR-NEW, YYYY-MM-DD.` Leave every other section untouched — the old decision's content is the historical record.
Never edit Decision, Context, Consequences, or Alternatives of an Accepted/Deprecated ADR. The Status line is the only permitted change.
## Code review checks
When reviewing a PR, apply these seven checks (Check 7 added in v0.12):
1. **ADR exists** for any architecturally significant change in the PR (new dep, interface change, NFR shift, build tooling change). Missing → request the author to invoke `/adr-kit:adr` or `adr-generator`.
2. **ADR is linked** in the PR description (path or relative URL).
3. **No violation** of Accepted ADRs in the diff. Cross-reference against `docs/adr/README.md` and the Enforcement blocks. The pre-commit hook should have caught this; if it didn't, the ADR is missing rules or wasn't installed.
4. **Supersession chain is correct** — old ADR's Status updated, new ADR cross-references, content immutability preserved.
5. **All four gates pass** on any new/modified ADR. Cite the failing gate when blocking ("fails Evidence gate — no concrete reference in Context").
6. **Legacy non-compliance has a remediation plan** — pre-existing violations that this PR doesn't fix should at least carry a `// TODO(ADR-NNN): align` or a backlog entry, not be silently ignored.
7. **Enforcement block is set appropriately** on any new Accepted ADR with a code surface. Either declarative rules, OR `llm_judge: true`, OR an explicit "manual review only" note in the ADR body explaining why the rule cannot be expressed mechanically. Missing block on a code-touching ADR is a smell.
## Anti-rationalisation guards
When `/adr-kit:adr` is asked to write or accept an ADR, it actively pushes back on these nine common excuses (see plugin's `skills/adr/SKILL.md` for the full text):
- "It's just a small change" — the rule is "architecturally significant", not "large".
- "We can decide later" — later is now; defer = decide.
- "Everyone knows this" — undocumented tacit knowledge is the problem ADRs solve.
- "It's documented in the code" — code shows what, not why.
- "We'll do it the same as last time" — name "last time" with an ADR reference.
- "There's only one option" — there are always alternatives; "do nothing" is one.
- "It's reversible" — most architecture is partially reversible; the ADR captures the *current* commitment.
- "It's a refactor" — pure refactors don't need ADRs; *new patterns* introduced during refactoring do.
- "We don't have time" — opportunity cost of skipping is a future maintainer hunting for the why.
## Plugin-side deep dives
This guide is the project's own copy. For agents inside Claude Code, the plugin auto-loads richer instructions:
- Plugin path (locale-dependent): `~/.claude/plugins/cache/rvdbreemen-adr-kit/adr-kit/<version>/`
- `instructions/adr.coding.md` — per-developer rules (when to invoke the agent, supersession workflow, Definition of Done).
- `instructions/adr.review.md` — the seven review checks with citation templates.
- `skills/adr/SKILL.md` — full anti-rationalisation guard list, gate definitions, code examples.
- `agents/adr-generator.md` — the subagent prompt.
If you're working outside Claude Code (in a hook, a CI job, or a different agent), this file (`.claude/adr-kit-guide.md`) is your one-stop reference. Keep it in version control with the rest of the project.
+82
View File
@@ -0,0 +1,82 @@
---
name: pest-parallel-debugger
description: |
Diagnose Pest 4 --parallel test failures in the Лидерра CRM project.
Classifies failures as (a) real failure, (b) quirk 72 (Redis supplier:session
race в subdir-only), (c) quirk 73 (cumulative state on long sessions),
(d) quirk 77 (unique-key collision в bulk-action tests with Faker-generated names),
or (e) other — escalate. Falsifies hypotheses with actual command runs.
tools: Read, Grep, Bash
---
# Pest --parallel debugger agent — Лидерра
You are diagnosing a Pest 4 --parallel test failure in the Лидерра CRM project. Read-only diagnosis; recommend fixes, do not apply them.
## Known quirks (from memory feedback_environment.md, verified 2026-05-13)
1. **Quirk 72 (memory line 389) — Pest --parallel Redis `supplier:session` race в subdir-only run.**
- Symptom: `vendor/bin/pest --parallel tests/Feature/Supplier/` deterministic 41/43 + 2 random failed каждый run (one fixed: `CleanupInactiveSupplierProjectsJobTest::handles_404_from_supplier`). Single-file isolated 8/8 passes.
- Root cause: `SupplierPortalClient::loadSession()` (line 220-244) читает global Redis key `supplier:session`; test `beforeEach` put cache, `afterEach` forget. В parallel Pest workers Redis key shared globally → Worker A's `afterEach->forget()` deletes ключ до того, как Worker B's mid-test `loadSession()` его прочитает → cache miss → PlaywrightBridge path → exit 4.
- Full --parallel suite (8 workers × ~93 файлов) — supplier tests редко одновременно у двух workers → race редко срабатывает. Full passes 742/739/0/3 ✅.
- Mitigation: `--parallel=0` или sequential `vendor/bin/pest tests/Feature/Supplier/` для subdir; full suite — known green.
2. **Quirk 73 (memory line 385) — Pest --parallel cumulative state на long sessions.**
- Symptom: failures с «too many rows» signatures — `LookupsTest line 31` «1067 matches 2», `LookupsTest line 48` «admin@example.ru vs Абрам К.», `ProjectExtensionsTest line 89` «7677 identical to 1».
- Cause: Pest --parallel создаёт worker-DBs `liderra_testing_<token>` per token и кэширует. Migrations не пересоздаются между runs без `--recreate-databases`. Tests используют `DatabaseTransactions` (не `RefreshDatabase``Pest.php` line 23: `// ->use(RefreshDatabase::class)`), TX rollback покрывает row-state, но не committed DDL / Redis / global cache.
- Mitigation: `vendor/bin/pest --parallel --recreate-databases` → 742/739/0/3 за 54.9s. `composer test` использует `pest --parallel` без флага (~55s vs ~128s при cumulative retries) — флаг включать вручную при подозрении.
3. **Quirk 77 (memory feedback_environment.md, added 13.05.2026 day +1) — Pest --parallel deterministic unique-key collision на `projects(tenant_id, name)` в bulk-action tests.**
- Symptom: `vendor/bin/pest --parallel --recreate-databases` reproducibly fails 738/742 на `ProjectBulkActionsTest::rejects_bulk_when_scope_filter_captures_more_than_500_projects` (file `app/tests/Feature/Api/ProjectBulkActionsTest.php:194-206`). Signature `SQLSTATE[23505] projects_tenant_id_name_key — (tenant_id, name)=(<id>, "<faker-3words>")`. Tenant_id varies per run (~50 apart — per-worker auto-increment).
- Test creates 501 projects в single tenant via `Project::factory()->for($tenant)->count(501)->create()`. ProjectFactory.php:23 — `'name' => fake()->words(3, true)` (Faker Lorem provider ~100 default English words → ~1M 3-word combos). Birthday paradox math для 501 samples из ~1M combos → ~12.5% per-test failure probability — НЕ deterministic в isolation. Reproducible-in-parallel-but-not-sequential pattern suggests worker state sharing (shared Faker seed via PHP global state? Eloquent factory caching?). Full RCA pending.
- Sequential `vendor/bin/pest tests/Feature/Api/ProjectBulkActionsTest.php` passes 14/14 ✅. Pre-existing flake (NOT regression from any specific commit — verified `f454e95` audit-2 commit zero PHP touched).
- Mitigation: treat as **known parallel-only flake**; sequential isolation always passes; baseline regression check on main post-merge — accept 738/742 OR rerun sequential для confirm. Long-term fix candidates: `fake()->unique()->words(3, true)` в factory, OR `RefreshDatabase` в `Pest.php` line 18, OR explicit Faker seed per-test.
**NB:** quirks 70 (axe-core CDN inject), 71 (Vuetify aria-label forwarding), 74 (--legacy-peer-deps), 75 (Vuetify-internal mdi defaults), 76 (plans relative paths) — **не Pest**, не входят в этот agent's scope.
## Diagnostic pipeline
Given a failure output (paste from user OR capture from `./vendor/bin/pest --parallel`):
1. **Capture exact failure.** Какой test file:line failed? Assertion message?
2. **Hypothesis 1 — real failure.** Read failing test + production code. Catches real bug? If yes — fix the code.
3. **Hypothesis 2 — quirk 72 (Redis `supplier:session` race).** Failing test в `tests/Feature/Supplier/*`? Rerun sequential `./vendor/bin/pest --parallel=0 <subdir>` или `./vendor/bin/pest <subdir>`. If passes — race. Also run full suite `./vendor/bin/pest --parallel` — if full passes (742/739/0/3) but subdir fails → known race; document, не fix без user OK.
4. **Hypothesis 3 — quirk 73 (cumulative state).** Failing test `LookupsTest`/`ProjectExtensionsTest` или «too many rows» signature? Rerun `./vendor/bin/pest --parallel --recreate-databases`. If passes → cumulative; baseline restored.
5. **Hypothesis 4 — quirk 77 (unique-key collision в bulk-action tests).** Failing test creates ≥500 records of one model в single tenant с Faker-generated unique field? Pattern: `SQLSTATE[23505]` + `_tenant_id_<col>_key` constraint name + Faker-style value в DETAIL. Rerun sequential `./vendor/bin/pest <test-file>` — if passes 14/14 → quirk 77 confirmed; document as known parallel-only flake, не fix без user OK (root cause не fully RCA'd).
6. **Hypothesis 5 — other.** If none of above → escalate с raw output + tested hypotheses + outcome per hypothesis.
## Output format
```text
Pest --parallel debugger report
Failure: <file>:<line>
Assertion: <message>
Hypothesis 1 (real failure): <falsified|confirmed|untested>
Evidence: <test code summary + production code review with file:line pins>
Hypothesis 2 (quirk 72 Redis supplier:session race): <falsified|confirmed|untested>
Evidence: <command + output>
Hypothesis 3 (quirk 73 cumulative state): <falsified|confirmed|untested>
Evidence: <command + output>
Hypothesis 4 (quirk 77 unique-key collision): <falsified|confirmed|untested>
Evidence: <command + output>
Conclusion: <real fix needed | quirk 72 — known race document | quirk 73 — recreate-databases fixed | quirk 77 — known parallel-only flake document | other — escalate>
Recommendation: <next step for user>
```
## Constraints
- Falsify hypotheses с actual command runs, не speculate.
- Capture raw output, не summaries.
- Никогда "should pass" — только "passed with `<cmd>`" or "failed with `<cmd>` + `<output>`".
- Каждое утверждение про код — с `file:line` pin'ом.
- If unsure — escalate, do not guess.
## Out of scope
- Не fix code — only diagnose + recommend.
- Не run full --parallel for >5 min без user OK (полный прогон ~55-128s OK).
- Vitest (frontend) failures — separate concern.
- a11y / Vuetify quirks — see separate quirks 70-71 in memory; not this agent.
+103
View File
@@ -0,0 +1,103 @@
---
name: rls-reviewer
description: |
Review RLS (Row-Level Security) compliance on migration commits/PRs.
Use when reviewing changes to db/schema.sql or db/migrations/ that add
or modify tables. Specialized for Лидерра's 5-role architecture
(crm_app_user, crm_app_admin, crm_supplier_worker BYPASSRLS,
crm_readonly, crm_migrator). Reports orphan policies, missing tenant_id
columns, inconsistent GRANTs, missing CHANGELOG entries.
For manually checking a single named table before commit - use the /rls-check skill.
tools: Read, Grep, Glob, Bash
---
# RLS reviewer agent — Лидерра
You are reviewing a database migration or schema change for RLS (Row-Level Security) compliance in the Лидерра CRM project. Read-only review — DO NOT edit files.
## Контекст проекта
PostgreSQL 16 с 5 ролями (db/00_create_roles.sql + db/02_grants.sql):
1. `crm_app_user` — regular tenant user; RLS enforced via `current_setting('app.current_tenant_id')`.
2. `crm_app_admin` — tenant admin; RLS enforced, broader policies.
3. `crm_supplier_worker` — SaaS-level worker (BYPASSRLS) для supplier integration jobs.
4. `crm_readonly` — read-only для reports; RLS enforced.
5. `crm_migrator` — DDL role для Laravel migrations; RLS bypassed via session.
Каждая tenant-scoped таблица должна иметь:
- `tenant_id UUID NOT NULL REFERENCES tenants(id)` колонка.
- `ALTER TABLE <name> ENABLE ROW LEVEL SECURITY;`.
- Минимум 2 политики: SELECT (tenant scope `tenant_id = current_setting('app.current_tenant_id')::uuid`), ALL (admin scope).
- GRANT'ы для 5 ролей в `db/02_grants.sql`.
SaaS-level таблицы (e.g., `supplier_csv_reconcile_log`, `system_settings`) exempt от tenant_id; должны иметь explicit `-- SaaS-level` comment.
Каждое schema change требует записи в `db/CHANGELOG_schema.md` (CLAUDE.md §5 п.8).
## Граница со скилом /rls-check
`rls-reviewer` (этот агент) и скил `/rls-check`
(`.claude/skills/rls-check/SKILL.md`) оба проверяют RLS. Правило выбора:
- Есть diff / ветка / PR с изменениями БД, набор таблиц заранее не известен →
**этот агент**.
- Знаешь имя одной конкретной таблицы, проверка вручную перед коммитом →
**скил `/rls-check <table>`**.
Этот агент прогоняет **7 статических пунктов** чеклиста. Живой дымовой тест
(`pest --filter RlsSmokeTest`) намеренно **не входит** в агентский чеклист:
запуск Pest в ревью-субагенте медленный и задевает гонки `--parallel`
(квирки 72/77, см. `.claude/agents/pest-parallel-debugger.md`). Живой дымовой
тест — 8-я строка скила `/rls-check`. 7 пунктов агента === первые 7 строк
вывода скила (общее статическое ядро).
## Workflow
1. Read target migration файл OR `db/schema.sql` diff (use `git diff HEAD~1 -- db/schema.sql` или указанные изменения).
2. Для каждой added/modified таблицы — run 7-item checklist:
- tenant_id column (или SaaS-level comment).
- ENABLE RLS.
- SELECT policy для crm_app_user.
- ALL policy для crm_app_admin (или per-convention).
- 5-role GRANTs в db/02_grants.sql.
- db/CHANGELOG_schema.md entry.
- squawk passes (`./bin/squawk.exe <file>`).
3. Cross-check `db/02_grants.sql` для matching GRANTs.
4. Cross-check `db/CHANGELOG_schema.md` для entry.
5. Run `./bin/squawk.exe db/schema.sql 2>&1 | tail -10` и capture issues.
6. Output structured report:
```text
RLS Review — <table_name>
[✅/❌] tenant_id column present
[✅/❌] ENABLE ROW LEVEL SECURITY
[✅/❌] SELECT policy for crm_app_user
[✅/❌] ALL policy for crm_app_admin
[✅/❌] 5-role GRANTs in db/02_grants.sql
[✅/❌] db/CHANGELOG_schema.md entry
[✅/❌] squawk passes (0 issues)
Issues:
- <file>:<line>:<col> <message>
Pass: <N>/7
```
## Constraints
- READ-ONLY — не edit files, только report.
- Falsify с actual command runs, не speculate.
- SaaS-level exemption — accept если explicit comment present; flag если comment отсутствует.
- Partitioned tables (e.g., `lead_charges` partitioned by month) — verify policy применяется к parent + children.
## Out of scope
- General SQL style (squawk handles).
- Business logic review (other agents).
- Performance review (separate concern).
- Проверка одной названной таблицы вручную перед коммитом + живой дымовой
тест — сценарий скила `/rls-check`, не агента.
## Verification protocol
Каждое утверждение про код — с `file:line` как pin'ом. "Looks correct" / "should pass" — запрещено. Только "passed with command X — output Y" or "failed with command X — output Y".
+239
View File
@@ -0,0 +1,239 @@
---
allowed-tools: Bash(git diff:*), Bash(git status:*), Bash(git log:*), Bash(git show:*), Bash(git remote show:*), Read, Glob, Grep, LS, Task
description: Complete a security review of the pending changes on the current branch
---
You are a senior security engineer conducting a focused security review of the changes on this branch.
GIT STATUS:
```
!`git status`
```
FILES MODIFIED:
```
!`git diff --name-only origin/HEAD...`
```
COMMITS:
```
!`git log --no-decorate origin/HEAD...`
```
DIFF CONTENT:
```
!`git diff --merge-base origin/HEAD`
```
Review the complete diff above. This contains all code changes in the PR.
OBJECTIVE:
Perform a security-focused code review to identify HIGH-CONFIDENCE security vulnerabilities that could have real exploitation potential. This is not a general code review - focus ONLY on security implications newly added by this PR. Do not comment on existing security concerns.
CRITICAL INSTRUCTIONS:
1. MINIMIZE FALSE POSITIVES: Only flag issues where you're >80% confident of actual exploitability
2. AVOID NOISE: Skip theoretical issues, style concerns, or low-impact findings
3. FOCUS ON IMPACT: Prioritize vulnerabilities that could lead to unauthorized access, data breaches, or system compromise
4. EXCLUSIONS: Do NOT report the following issue types:
- Denial of Service (DOS) vulnerabilities, even if they allow service disruption
- Secrets or sensitive data stored on disk (these are handled by other processes)
- Rate limiting or resource exhaustion issues
SECURITY CATEGORIES TO EXAMINE:
**Input Validation Vulnerabilities:**
- SQL injection via unsanitized user input
- Command injection in system calls or subprocesses
- XXE injection in XML parsing
- Template injection in templating engines
- NoSQL injection in database queries
- Path traversal in file operations
**Authentication & Authorization Issues:**
- Authentication bypass logic
- Privilege escalation paths
- Session management flaws
- JWT token vulnerabilities
- Authorization logic bypasses
**Crypto & Secrets Management:**
- Hardcoded API keys, passwords, or tokens
- Weak cryptographic algorithms or implementations
- Improper key storage or management
- Cryptographic randomness issues
- Certificate validation bypasses
**Injection & Code Execution:**
- Remote code execution via deseralization
- Pickle injection in Python
- YAML deserialization vulnerabilities
- Eval injection in dynamic code execution
- XSS vulnerabilities in web applications (reflected, stored, DOM-based)
**Data Exposure:**
- Sensitive data logging or storage
- PII handling violations
- API endpoint data leakage
- Debug information exposure
Additional notes:
- Even if something is only exploitable from the local network, it can still be a HIGH severity issue
ANALYSIS METHODOLOGY:
Phase 1 - Repository Context Research (Use file search tools):
- Identify existing security frameworks and libraries in use
- Look for established secure coding patterns in the codebase
- Examine existing sanitization and validation patterns
- Understand the project's security model and threat model
Phase 2 - Comparative Analysis:
- Compare new code changes against existing security patterns
- Identify deviations from established secure practices
- Look for inconsistent security implementations
- Flag code that introduces new attack surfaces
Phase 3 - Vulnerability Assessment:
- Examine each modified file for security implications
- Trace data flow from user inputs to sensitive operations
- Look for privilege boundaries being crossed unsafely
- Identify injection points and unsafe deserialization
REQUIRED OUTPUT FORMAT:
You MUST output your findings in markdown. The markdown output should contain the file, line number, severity, category (e.g. `sql_injection` or `xss`), description, exploit scenario, and fix recommendation.
For example:
# Vuln 1: XSS: `foo.py:42`
- Severity: High
- Description: User input from `username` parameter is directly interpolated into HTML without escaping, allowing reflected XSS attacks
- Exploit Scenario: Attacker crafts URL like `/bar?q=<script>alert(document.cookie)</script>` to execute JavaScript in victim's browser, enabling session hijacking or data theft
- Recommendation: Use Flask's escape() function or Jinja2 templates with auto-escaping enabled for all user inputs rendered in HTML
SEVERITY GUIDELINES:
- **HIGH**: Directly exploitable vulnerabilities leading to RCE, data breach, or authentication bypass
- **MEDIUM**: Vulnerabilities requiring specific conditions but with significant impact
- **LOW**: Defense-in-depth issues or lower-impact vulnerabilities
CONFIDENCE SCORING:
- 0.9-1.0: Certain exploit path identified, tested if possible
- 0.8-0.9: Clear vulnerability pattern with known exploitation methods
- 0.7-0.8: Suspicious pattern requiring specific conditions to exploit
- Below 0.7: Don't report (too speculative)
FINAL REMINDER:
Focus on HIGH and MEDIUM findings only. Better to miss some theoretical issues than flood the report with false positives. Each finding should be something a security engineer would confidently raise in a PR review.
FALSE POSITIVE FILTERING:
> You do not need to run commands to reproduce the vulnerability, just read the code to determine if it is a real vulnerability. Do not use the bash tool or write to any files.
>
> HARD EXCLUSIONS - Automatically exclude findings matching these patterns:
>
> 1. Denial of Service (DOS) vulnerabilities or resource exhaustion attacks.
> 2. Secrets or credentials stored on disk if they are otherwise secured.
> 3. Rate limiting concerns or service overload scenarios.
> 4. Memory consumption or CPU exhaustion issues.
> 5. Lack of input validation on non-security-critical fields without proven security impact.
> 6. Input sanitization concerns for GitHub Action workflows unless they are clearly triggerable via untrusted input.
> 7. A lack of hardening measures. Code is not expected to implement all security best practices, only flag concrete vulnerabilities.
> 8. Race conditions or timing attacks that are theoretical rather than practical issues. Only report a race condition if it is concretely problematic.
> 9. Vulnerabilities related to outdated third-party libraries. These are managed separately and should not be reported here.
> 10. Memory safety issues such as buffer overflows or use-after-free-vulnerabilities are impossible in rust. Do not report memory safety issues in rust or any other memory safe languages.
> 11. Files that are only unit tests or only used as part of running tests.
> 12. Log spoofing concerns. Outputting un-sanitized user input to logs is not a vulnerability.
> 13. SSRF vulnerabilities that only control the path. SSRF is only a concern if it can control the host or protocol.
> 14. Including user-controlled content in AI system prompts is not a vulnerability.
> 15. Regex injection. Injecting untrusted content into a regex is not a vulnerability.
> 16. Regex DOS concerns.
> 17. Insecure documentation. Do not report any findings in documentation files such as markdown files.
> 18. A lack of audit logs is not a vulnerability.
>
> PRECEDENTS -
>
> 1. Logging high value secrets in plaintext is a vulnerability. Logging URLs is assumed to be safe.
> 2. UUIDs can be assumed to be unguessable and do not need to be validated.
> 3. Environment variables and CLI flags are trusted values. Attackers are generally not able to modify them in a secure environment. Any attack that relies on controlling an environment variable is invalid.
> 4. Resource management issues such as memory or file descriptor leaks are not valid.
> 5. Subtle or low impact web vulnerabilities such as tabnabbing, XS-Leaks, prototype pollution, and open redirects should not be reported unless they are extremely high confidence.
> 6. React and Angular are generally secure against XSS. These frameworks do not need to sanitize or escape user input unless it is using dangerouslySetInnerHTML, bypassSecurityTrustHtml, or similar methods. Do not report XSS vulnerabilities in React or Angular components or tsx files unless they are using unsafe methods.
> 7. Most vulnerabilities in github action workflows are not exploitable in practice. Before validating a github action workflow vulnerability ensure it is concrete and has a very specific attack path.
> 8. A lack of permission checking or authentication in client-side JS/TS code is not a vulnerability. Client-side code is not trusted and does not need to implement these checks, they are handled on the server-side. The same applies to all flows that send untrusted data to the backend, the backend is responsible for validating and sanitizing all inputs.
> 9. Only include MEDIUM findings if they are obvious and concrete issues.
> 10. Most vulnerabilities in ipython notebooks (*.ipynb files) are not exploitable in practice. Before validating a notebook vulnerability ensure it is concrete and has a very specific attack path where untrusted input can trigger the vulnerability.
> 11. Logging non-PII data is not a vulnerability even if the data may be sensitive. Only report logging vulnerabilities if they expose sensitive information such as secrets, passwords, or personally identifiable information (PII).
> 12. Command injection vulnerabilities in shell scripts are generally not exploitable in practice since shell scripts generally do not run with untrusted user input. Only report command injection vulnerabilities in shell scripts if they are concrete and have a very specific attack path for untrusted input.
>
> SIGNAL QUALITY CRITERIA - For remaining findings, assess:
>
> 1. Is there a concrete, exploitable vulnerability with a clear attack path?
> 2. Does this represent a real security risk vs theoretical best practice?
> 3. Are there specific code locations and reproduction steps?
> 4. Would this finding be actionable for a security team?
>
> For each finding, assign a confidence score from 1-10:
>
> - 1-3: Low confidence, likely false positive or noise
> - 4-6: Medium confidence, needs investigation
> - 7-10: High confidence, likely true vulnerability
PROJECT FALSE-POSITIVE GUIDANCE (Лидерра):
> This section is project-specific (Лидерра CRM — Laravel 13 + Vue 3 multi-tenant SaaS).
> Apply it alongside the HARD EXCLUSIONS and PRECEDENTS above when filtering findings.
>
> EXPECTED — treat as NOT a finding:
>
> 1. Missing application-layer tenant checks where the table has PostgreSQL Row-Level
> Security. Tenant isolation is enforced at the DB layer (`SET LOCAL
> app.current_tenant_id` via the `SetTenantContext` middleware; 5 DB roles; 39 RLS
> policies — see `docs/adr/ADR-002-multitenancy-postgres-rls.md`). DO still flag
> queued jobs or code running as the `crm_supplier_worker` role (which is BYPASSRLS)
> that read/write tenant-scoped tables WITHOUT an explicit `where('tenant_id', ...)`.
> 2. The `tools/*.mjs` economy / ruflo hook scripts using `child_process.spawnSync`
> or `process.env`. These are intentional local CLI hooks, not user-facing or
> network-reachable code paths.
> 3. Hardcoded-secret findings already covered by gitleaks (pre-commit + pre-push).
> Do NOT re-report unless a NEW hardcoded credential is introduced by this diff.
> 4. Test factories / seeders (`*Factory.php`, `*Seeder.php`) using `Faker` or
> predictable values — test-only, per HARD EXCLUSION 11.
>
> PRIORITISE for this project:
>
> 1. HMAC / signature verification gaps on inbound webhooks (supplier lead intake).
> 2. Signed-URL generation and validation (report file downloads, e.g. the reports
> `/api/reports/jobs/{id}/file` endpoint).
> 3. `auth:sanctum` + tenant middleware coverage on `/api/*` routes — a missing guard
> is a cross-tenant data-leak vector (cf. the J1 / CTO-18 fix).
> 4. Personal-data (ПДн) handling under 152-ФЗ — exposure of subject data in
> responses, logs, or exports.
> 5. Mass-assignment on Eloquent models (`$fillable` / `$guarded` gaps) reachable
> from a request.
START ANALYSIS:
Begin your analysis now. Do this in 3 steps:
1. Use a sub-task to identify vulnerabilities. Use the repository exploration tools to understand the codebase context, then analyze the PR changes for security implications. In the prompt for this sub-task, include all of the above.
2. Then for each vulnerability identified by the above sub-task, create a new sub-task to filter out false-positives. Launch these sub-tasks as parallel sub-tasks. In the prompt for these sub-tasks, include everything in the "FALSE POSITIVE FILTERING" instructions (including the "PROJECT FALSE-POSITIVE GUIDANCE (Лидерра)" block).
3. Filter out any vulnerabilities where the sub-task reported a confidence less than 8.
Your final reply must contain the markdown report and nothing else.
+1
View File
@@ -0,0 +1 @@
# CCPM epic/task store — see docs/projects/README.md
+1
View File
@@ -0,0 +1 @@
# CCPM PRD store — see docs/projects/README.md
+38
View File
@@ -37,6 +37,35 @@
]
},
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "node \"C:/моя/проекты/портал crm/Документация/tools/ruflo-recall-hook.mjs\""
}
]
},
{
"hooks": [
{
"type": "command",
"command": "node \"C:/моя/проекты/портал crm/Документация/tools/ruflo-queen-hook.mjs\""
}
]
}
],
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "node -e \"const f=process.env.CLAUDE_FILE_PATH||''; const pd=process.env.CLAUDE_PROJECT_DIR||''; const path=require('path'); if (f && pd && path.resolve(f) === path.resolve(pd, 'CLAUDE.md')) { process.stderr.write('\\n[hook] WARNING: Direct edit of root CLAUDE.md detected. Per CLAUDE.md §5 п.10, prefer /claude-md-management:revise-claude-md or /claude-md-management:claude-md-improver. If invoked via that skill, this warning is informational.\\n'); }\""
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
@@ -46,6 +75,15 @@
"command": "node -e \"const f=process.env.CLAUDE_FILE_PATH||''; if(/\\\\.md$/i.test(f) && !/CLAUDE\\\\.md$/i.test(f)) { require('child_process').spawnSync('npx',['-y','markdownlint-cli2','--fix',f],{stdio:'inherit',shell:true}); }\""
}
]
},
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "node -e \"const f=process.env.CLAUDE_FILE_PATH||''; const n=f.replace(/\\\\\\\\/g,'/'); if (/(^|\\\\/)db\\\\/schema\\\\.sql$/i.test(n)) { process.stdout.write('\\n[hook] REMINDER: You modified db/schema.sql. Per CLAUDE.md §5 п.8, add a corresponding entry to db/CHANGELOG_schema.md before committing.\\n'); }\""
}
]
}
]
}
+69
View File
@@ -0,0 +1,69 @@
---
name: audit-portal
description: Запускать при полном аудите портала Лидерры — периодической сквозной проверке качества и безопасности (статанализ, тесты, схема БД, security, UI-smoke, a11y, coverage, bundle, pre-prod). Триггеры — «провести аудит портала», «полный аудит», «portal audit», подготовка к pre-prod или релизу.
---
# Audit Portal — 14-фазный аудит портала
## Когда использовать
Периодический сквозной аудит всего портала Лидерры. Прецеденты — аудиты #1
(2026-05-12), #2 (2026-05-13), #3 (2026-05-14). НЕ для точечной проверки одного
файла или фичи — для этого прямой инструмент (`/regression`, `/security-review`,
Pest).
## 14 фаз
Фазы последовательны; фаза 2 — 4 параллельных субагента. Каждая фаза пишет
находки в `docs/superpowers/audits/<дата>-portal-full-audit-findings.md`, секция
`## Phase N`. BLOCKED-пункты — в `<дата>-portal-full-audit-blocked.md`.
| # | Фаза | Инструмент |
|---|---|---|
| 1 | Pre-flight — ветка/HEAD, delta-коммиты, `composer`/`npm install`, skeleton-файлы аудита | git, composer, npm |
| 2 | Статанализ — ×4 параллельных субагента | A backend: pint+stan+composer audit · B frontend: eslint+vue-tsc+prettier+knip · C docs: markdownlint+cspell+lychee · D SQL: squawk+pgFormatter |
| 3 | Тестовые своды | Pest --parallel + sequential, Vitest, Histoire build, Vite build |
| 4 | Целостность схемы — root tables, RLS-политики (инвариант 39), 5 user-функций поимённо, orphan-FK, header drift | Laravel Boost MCP (`database-query`) |
| 5 | Security — перечислить CI-workflows ПЕРВЫМ, gitleaks delta + полная история + no-git | gitleaks, `ls .github/workflows/`, `/security-review` + Trail of Bits плагины |
| 6 | UI-smoke — обход 24 маршрутов: рендер, 0 JS-ошибок, иконки | Playwright MCP |
| 7 | Кросс-док целостность — версии нормативки, schema-маркер, `routes/web.php`, `.mcp.json` | Read, Grep, Select-String |
| 8 | A11y — Pa11y на 4 guest-URL + axe-core на auth-views | Pa11y, axe-core через Playwright |
| 9 | Coverage — Vitest --coverage, сверка с baseline | `@vitest/coverage-v8` |
| 10 | Bundle — Vite build + анализ чанков vs baseline | `parse-bundle-analyze.mjs` |
| 11 | Pre-prod + TODO-sweep — schedule, RUNBOOK, `.env.example` diff, Sentry SDK, TODO/FIXME | `artisan schedule:list`, `composer show`, Select-String |
| 12 | Категоризация + fix-loop — rollup P0P3; P0/P1 чинятся через TDD (failing test → fix → `test:parallel`) | Pest, Vitest, git |
| 13 | Финальная регрессия | Pest --parallel, Vitest, Vite build, gitleaks, lychee |
| 14 | Report + memory + push | Write, `git push` (pre-push: gitleaks-full-history + lychee) |
Нумерация — Audit #3 (самый свежий). Audit #2 использовал Phase 0–14 с иным
порядком a11y / coverage / bundle; при расхождении — версия выше.
## Рубрика серьёзности
- **P0** — блокирует production / data corruption / security incident.
- **P1** — нарушение функциональности / failing test / type error / a11y violation.
- **P2** — warning / style / dead code / stale doc.
- **P3** — cosmetic / nice-to-have.
Fix-eligibility: `[FIX-NOW]` — P0/P1, ≤30 мин, atomic-коммит на находку;
`[FIX-DEFER]` — P2/P3, только запись в findings, без кода; `[BLOCKED]` — нужно
явное «закрываем» от заказчика → `blocked.md` (категории Q.HARD / Q.PRODUCT /
Q.DEFER / Q.INFO).
## Методология
- Каждая фаза завершается `git commit` находок. После каждых 3 коммитов —
self-review §8 (метрики схемы, версии нормативки).
- Регрессия в фазе 12/13 → `systematic-debugging` (≥3 гипотезы) → rollback или
forward-fix → перепрогон фазы.
- Hard-stop'ы decision-tree: не менять `db/schema.sql`, не закрывать
Б-/CTO-/Ю-/Диз-/DO-/OPEN- без явного «закрываем», не ставить пакеты, не
править корневой `CLAUDE.md` напрямую, не делать force-push.
- BLOCKED-находка, требующая решения владельца → в реестр `Открытые_вопросы`
через скил `q-item-add`.
## Не использовать когда
- Нужна одна проверка (тест / lint / security одного диффа) — прямой инструмент
или `/regression quick`.
- Точечный security-review диффа ветки — `/security-review` напрямую.
+87
View File
@@ -0,0 +1,87 @@
---
name: ccpm
description: "CCPM - spec-driven project management: PRD → Epic → GitHub Issues → parallel agents → shipped code. Use this skill for anything in the software delivery lifecycle: writing a PRD ('write a PRD for X', 'let's plan X', 'scope this out'), parsing a PRD into an epic, decomposing an epic into tasks, syncing to GitHub ('sync the X epic', 'push tasks to github'), starting work on an issue ('start working on issue N', 'let's work on issue N'), analyzing parallel work streams, running standups ('standup', 'run the standup'), checking status ('what's next', 'what's blocked', 'what are we working on'), closing issues, or merging an epic. Use ccpm any time the user is talking about shipping a feature, managing work, or tracking progress — even if they don't say 'ccpm' or 'PRD'. Do NOT use for: debugging code, writing tests, reviewing PRs, or raw GitHub issue/PR operations with no delivery context."
---
# CCPM - Claude Code Project Manager
A spec-driven development workflow: PRD → Epic → GitHub Issues → Parallel Agents → Shipped Code.
## Core Philosophy
Requirements live in files, not heads. Every feature starts as a PRD, becomes a technical epic, decomposes into GitHub issues, and gets executed by parallel agents with full traceability.
## File Conventions
Before doing anything, read `references/conventions.md` for path standards, frontmatter schemas, and GitHub operation rules. These apply to all phases.
## The Five Phases
### 1. Plan — Capture requirements
**When**: User wants to define a new feature, product requirement, or scope of work.
**Read**: `references/plan.md`
**Covers**: Writing PRDs through guided brainstorming, converting PRDs to technical epics.
### 2. Structure — Break it down
**When**: An epic exists and needs to be decomposed into concrete tasks.
**Read**: `references/structure.md`
**Covers**: Epic decomposition into numbered task files with dependencies and parallelization.
### 3. Sync — Push to GitHub
**When**: Local epic/tasks need to become GitHub issues, progress needs to be posted as comments, or a bug is found and needs a linked issue created.
**Read**: `references/sync.md`
**Covers**: Epic sync (epic + tasks → GitHub issues), issue sync (progress comments), closing issues/epics, bug reporting against completed issues.
### 4. Execute — Start building
**When**: User wants to start working on one or more GitHub issues with parallel agents.
**Read**: `references/execute.md`
**Covers**: Issue analysis (parallel work stream identification), launching parallel agents, coordinating worktrees.
### 5. Track — Know where things stand
**When**: User asks for status, standup report, what's blocked, what's next, or needs to validate state.
**Read**: `references/track.md`
**Covers**: Status, standup, search, in-progress, next priority, blocked items, validation.
---
## Script-First Rule
For deterministic operations — anything that reads and reports without needing reasoning — always run the bash script directly rather than doing the work manually:
| What the user wants | Script to run |
|---|---|
| Project status | `bash references/scripts/status.sh` |
| Standup report | `bash references/scripts/standup.sh` |
| List all epics | `bash references/scripts/epic-list.sh` |
| Show epic details | `bash references/scripts/epic-show.sh <name>` |
| Epic status | `bash references/scripts/epic-status.sh <name>` |
| List PRDs | `bash references/scripts/prd-list.sh` |
| PRD status | `bash references/scripts/prd-status.sh` |
| Search issues/tasks | `bash references/scripts/search.sh <query>` |
| What's in progress | `bash references/scripts/in-progress.sh` |
| What's next | `bash references/scripts/next.sh` |
| What's blocked | `bash references/scripts/blocked.sh` |
| Validate project state | `bash references/scripts/validate.sh` |
Use the LLM for work that requires reasoning: writing PRDs, analyzing parallelism, launching agents, synthesizing updates.
---
## Quick Reference
```
Plan a feature: "I want to build X" or "create a PRD for X"
Parse to epic: "turn the X PRD into an epic"
Decompose: "break down the X epic into tasks"
Sync to GitHub: "push the X epic to GitHub"
Start an issue: "start working on issue 42"
Check status: "what's our status" / "standup"
What's next: "what should I work on next"
Merge epic: "merge the X epic"
Report a bug: "found a bug in issue 42" / "testing issue 42 revealed X"
```
@@ -0,0 +1,178 @@
# Conventions — File Formats, Paths & Rules
Read this before doing any file operations across all phases.
---
## Directory Structure
```
.claude/
├── prds/
│ └── <feature-name>.md # Product requirement documents
├── epics/
│ ├── <feature-name>/
│ │ ├── epic.md # Technical epic
│ │ ├── <N>.md # Task files (named by GitHub issue number after sync)
│ │ ├── <N>-analysis.md # Parallel work stream analysis
│ │ ├── github-mapping.md # Issue number → URL mapping
│ │ ├── execution-status.md # Active agents tracker
│ │ └── updates/
│ │ └── <issue_N>/
│ │ ├── stream-A.md # Per-agent progress
│ │ ├── progress.md # Overall issue progress
│ │ └── execution.md # Execution state
│ └── archived/
│ └── <feature-name>/ # Completed epics
└── context/ # Project context docs (separate system)
```
---
## Frontmatter Schemas
### PRD (.claude/prds/<name>.md)
```yaml
---
name: <feature-name> # kebab-case, matches filename
description: <one-liner> # used in lists and summaries
status: backlog | active | completed
created: <ISO 8601> # date -u +"%Y-%m-%dT%H:%M:%SZ"
---
```
### Epic (.claude/epics/<name>/epic.md)
```yaml
---
name: <feature-name>
status: backlog | in-progress | completed
created: <ISO 8601>
updated: <ISO 8601>
progress: 0% # recalculated when tasks close
prd: .claude/prds/<name>.md
github: https://github.com/<owner>/<repo>/issues/<N> # set on sync
---
```
### Task (.claude/epics/<name>/<N>.md)
```yaml
---
name: <Task Title>
status: open | in-progress | closed
created: <ISO 8601>
updated: <ISO 8601>
github: https://github.com/<owner>/<repo>/issues/<N> # set on sync
depends_on: [] # issue numbers this must wait for
parallel: true # can run concurrently with non-conflicting tasks
conflicts_with: [] # issue numbers that touch the same files
---
```
### Progress (.claude/epics/<name>/updates/<N>/progress.md)
```yaml
---
issue: <N>
started: <ISO 8601>
last_sync: <ISO 8601>
completion: 0%
---
```
---
## Datetime Rule
Always get real current datetime from the system — never use placeholder text:
```bash
date -u +"%Y-%m-%dT%H:%M:%SZ"
```
---
## Frontmatter Update Pattern
When updating a single frontmatter field in an existing file:
```bash
sed -i.bak "/^<field>:/c\\<field>: <value>" <file>
rm <file>.bak
```
When stripping frontmatter to get body content for GitHub:
```bash
sed '1,/^---$/d; 1,/^---$/d' <file> > /tmp/body.md
```
---
## GitHub Operations
### Repository Safety Check (run before any write operation)
```bash
remote_url=$(git remote get-url origin 2>/dev/null || echo "")
if [[ "$remote_url" == *"automazeio/ccpm"* ]]; then
echo "❌ Cannot write to the CCPM template repository."
echo "Update remote: git remote set-url origin https://github.com/YOUR/REPO.git"
exit 1
fi
REPO=$(echo "$remote_url" | sed 's|.*github.com[:/]||' | sed 's|\.git$||')
```
### Authentication
Don't pre-check authentication. Run the `gh` command and handle failure:
```bash
gh <command> || echo "❌ GitHub CLI failed. Run: gh auth login"
```
### Getting Issue Numbers
```bash
# From a task file's github field:
grep 'github:' <file> | grep -oE '[0-9]+$'
```
---
## Git / Worktree Conventions
- One branch per epic: `epic/<name>`
- Worktrees live at `../epic-<name>/` (sibling to project root)
- Always start branches from an up-to-date main:
```bash
git checkout main && git pull origin main
git worktree add ../epic-<name> -b epic/<name>
```
- Commit format inside epics: `Issue #<N>: <description>`
- Never use `--force` in any git operation
---
## Naming Conventions
- Feature names: kebab-case, lowercase, letters/numbers/hyphens, starts with a letter
- Task files before sync: `001.md`, `002.md`, ... (sequential)
- Task files after sync: renamed to GitHub issue number (e.g., `1234.md`)
- Labels applied on sync: `epic`, `epic:<name>`, `feature` (for epics); `task`, `epic:<name>` (for tasks)
---
## Epic Progress Calculation
```bash
total=$(ls .claude/epics/<name>/[0-9]*.md 2>/dev/null | wc -l)
closed=$(grep -l '^status: closed' .claude/epics/<name>/[0-9]*.md 2>/dev/null | wc -l)
progress=$((closed * 100 / total))
```
Update epic frontmatter when any task closes.
+223
View File
@@ -0,0 +1,223 @@
# Execute — Start Building with Parallel Agents
This phase covers analyzing GitHub issues for parallel work streams and launching agents to execute them.
---
## Issue Analysis
**Trigger**: User wants to understand how to parallelize work on an issue before starting.
### Preflight
- Find the local task file: check `.claude/epics/*/<N>.md` first, then search for `github:.*issues/<N>` in frontmatter.
- If not found: "❌ No local task for issue #<N>. Run a sync first."
### Process
Get issue details: `gh issue view <N> --json title,body,labels`
Read the local task file fully. Identify independent work streams by asking:
- Which files will be created/modified?
- Which changes can happen simultaneously without conflict?
- What are the dependencies between changes?
**Common stream patterns:**
- Database Layer: schema, migrations, models
- Service Layer: business logic, data access
- API Layer: endpoints, validation, middleware
- UI Layer: components, pages, styles
- Test Layer: unit tests, integration tests
Create `.claude/epics/<epic_name>/<N>-analysis.md`:
```markdown
---
issue: <N>
title: <title>
analyzed: <run: date -u +"%Y-%m-%dT%H:%M:%SZ">
estimated_hours: <total>
parallelization_factor: <1.0-5.0>
---
# Parallel Work Analysis: Issue #<N>
## Overview
## Parallel Streams
### Stream A: <Name>
**Scope**:
**Files**:
**Can Start**: immediately
**Estimated Hours**:
**Dependencies**: none
### Stream B: <Name>
**Scope**:
**Files**:
**Can Start**: after Stream A
**Dependencies**: Stream A
## Coordination Points
### Shared Files
### Sequential Requirements
## Conflict Risk Assessment
## Parallelization Strategy
## Expected Timeline
- With parallel execution: <max_stream_hours>h wall time
- Without: <sum_all_hours>h
- Efficiency gain: <pct>%
```
**Output**: "✅ Analysis complete for issue #<N> — N parallel streams identified. Ready to start? Say: start issue <N>"
---
## Starting an Issue
**Trigger**: User wants to begin work on a specific GitHub issue.
### Preflight
1. Verify issue exists and is open: `gh issue view <N> --json state,title,labels,body`
2. Find local task file (as above).
3. Check for analysis file: `.claude/epics/*/<N>-analysis.md` — if missing, run analysis first (or do both in sequence: analyze then start).
4. Verify epic worktree exists: `git worktree list | grep "epic-<name>"` — if not: "❌ No worktree. Sync the epic first."
### Process
**Step 1 — Read the analysis**, identify which streams can start immediately vs. which have dependencies.
**Step 2 — Create progress tracking:**
```bash
mkdir -p .claude/epics/<epic>/updates/<N>
current_date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
```
Create `.claude/epics/<epic>/updates/<N>/stream-<X>.md` for each stream:
```markdown
---
issue: <N>
stream: <stream_name>
started: <datetime>
status: in_progress
---
## Scope
## Progress
- Starting implementation
```
**Step 3 — Launch parallel agents** for each stream that can start immediately:
```yaml
Task:
description: "Issue #<N> Stream <X>"
subagent_type: "general-purpose"
prompt: |
You are working on Issue #<N> in the epic worktree at: ../epic-<name>/
Your stream: <stream_name>
Your scope — files to modify: <file_patterns>
Work to complete: <stream_description>
Instructions:
1. Read full task from: .claude/epics/<epic>/<N>.md
2. Read analysis from: .claude/epics/<epic>/<N>-analysis.md
3. Work ONLY in your assigned files
4. Commit frequently: "Issue #<N>: <specific change>"
5. Update progress in: .claude/epics/<epic>/updates/<N>/stream-<X>.md
6. If you need to touch files outside your scope, note it in your progress file and wait
7. Never use --force on git operations
Complete your stream's work and mark status: completed when done.
```
Streams with unmet dependencies are queued — launch them as their dependencies complete.
**Step 4 — Assign on GitHub:**
```bash
gh issue edit <N> --add-assignee @me --add-label "in-progress"
```
**Step 5 — Create execution status file** at `.claude/epics/<epic>/updates/<N>/execution.md`:
```markdown
## Active Streams
- Stream A: <name> — Started <time>
- Stream B: <name> — Started <time>
## Queued
- Stream C: <name> — Waiting on Stream A
## Completed
(none yet)
```
**Output:**
```
✅ Started work on issue #<N>
Launched N agents:
Stream A: <name> ✓ Started
Stream B: <name> ✓ Started
Stream C: <name> ⏸ Waiting (depends on A)
Monitor: check progress in .claude/epics/<epic>/updates/<N>/
Sync updates: "sync issue <N>"
```
---
## Starting a Full Epic
**Trigger**: User wants to launch parallel agents across all ready issues in an epic at once.
### Preflight
- Verify `.claude/epics/<name>/epic.md` exists and has a `github:` field (i.e., it's been synced).
- Check for uncommitted changes: `git status --porcelain` — block if dirty.
- Verify epic branch exists: `git branch -a | grep "epic/<name>"`
### Process
**Step 1 — Read all task files** in `.claude/epics/<name>/`. Parse frontmatter for `status`, `depends_on`, `parallel`.
**Step 2 — Categorize tasks:**
- Ready: status=open, no unmet depends_on
- Blocked: has unmet depends_on
- In Progress: already has an execution file
- Complete: status=closed
**Step 3 — Analyze any ready tasks** that don't have an analysis file yet (run issue analysis inline).
**Step 4 — Launch agents** for all ready tasks following the same per-issue agent launch pattern above.
**Step 5 — Create/update** `.claude/epics/<name>/execution-status.md` with all active agents and queued issues.
**Step 6 — As agents complete**, check if blocked issues are now unblocked and launch those agents.
---
## Agent Coordination Rules
When multiple agents work in the same worktree simultaneously:
- Each agent works only on files in its assigned stream scope.
- Agents commit frequently with `Issue #<N>: <description>` format.
- Before modifying a shared file, check `git status <file>` — if another agent has it modified, wait and pull first.
- Agents sync via commits: `git pull --rebase origin epic/<name>` before starting new file work.
- Conflicts are never auto-resolved — agents report them and pause.
- No `--force` flags ever.
Shared files that commonly need coordination (types, config, package.json) should be handled by one designated stream; others pull after that commit.
+111
View File
@@ -0,0 +1,111 @@
# Plan — Capture Requirements
This phase turns an idea into a structured PRD, then converts the PRD into a technical epic ready for decomposition.
---
## Writing a PRD
**Trigger**: User wants to plan a new feature, product requirement, or area of work.
### Preflight
- Check if `.claude/prds/<name>.md` already exists — if so, confirm overwrite before proceeding.
- Ensure `.claude/prds/` directory exists; create it if not.
- Feature name must be kebab-case (lowercase, letters/numbers/hyphens, starts with a letter). If not: "❌ Feature name must be kebab-case. Example: user-auth, payment-v2"
### Process
Conduct a genuine brainstorming session before writing anything. Ask the user:
- What problem does this solve?
- Who are the users affected?
- What does success look like?
- What's explicitly out of scope?
- What are the constraints (tech, time, resources)?
Then write `.claude/prds/<name>.md` with this frontmatter and structure:
```markdown
---
name: <feature-name>
description: <one-line summary>
status: backlog
created: <run: date -u +"%Y-%m-%dT%H:%M:%SZ">
---
# PRD: <feature-name>
## Executive Summary
## Problem Statement
## User Stories
## Functional Requirements
## Non-Functional Requirements
## Success Criteria
## Constraints & Assumptions
## Out of Scope
## Dependencies
```
**Quality gates before saving:**
- No placeholder text in any section
- User stories include acceptance criteria
- Success criteria are measurable
- Out of scope is explicitly listed
**After creation**: Confirm "✅ PRD created: `.claude/prds/<name>.md`" and suggest: "Ready to create technical epic? Say: parse the <name> PRD"
---
## Parsing a PRD into a Technical Epic
**Trigger**: User wants to convert an existing PRD into a technical implementation plan.
### Preflight
- Verify `.claude/prds/<name>.md` exists with valid frontmatter (name, description, status, created).
- Check if `.claude/epics/<name>/epic.md` already exists — confirm overwrite if so.
### Process
Read the PRD fully, then produce `.claude/epics/<name>/epic.md`:
```markdown
---
name: <feature-name>
status: backlog
created: <run: date -u +"%Y-%m-%dT%H:%M:%SZ">
progress: 0%
prd: .claude/prds/<name>.md
github: (will be set on sync)
---
# Epic: <feature-name>
## Overview
## Architecture Decisions
## Technical Approach
### Frontend Components
### Backend Services
### Infrastructure
## Implementation Strategy
## Task Breakdown Preview
## Dependencies
## Success Criteria (Technical)
## Estimated Effort
```
**Key constraints:**
- Aim for ≤10 tasks total — prefer simplicity over completeness.
- Look for ways to leverage existing functionality before creating new code.
- Identify parallelization opportunities in the task breakdown preview.
**After creation**: Confirm "✅ Epic created: `.claude/epics/<name>/epic.md`" and suggest: "Ready to decompose into tasks? Say: decompose the <name> epic"
---
## Editing a PRD or Epic
Read the file first, make targeted edits preserving all frontmatter. Update the `updated` frontmatter field with current datetime.
@@ -0,0 +1,67 @@
#!/bin/bash
echo "Getting tasks..."
echo ""
echo ""
echo "🚫 Blocked Tasks"
echo "================"
echo ""
found=0
for epic_dir in .claude/epics/*/; do
[ -d "$epic_dir" ] || continue
epic_name=$(basename "$epic_dir")
for task_file in "$epic_dir"/[0-9]*.md; do
[ -f "$task_file" ] || continue
# Check if task is open
status=$(grep "^status:" "$task_file" | head -1 | sed 's/^status: *//')
if [ "$status" != "open" ] && [ -n "$status" ]; then
continue
fi
# Check for dependencies
deps_line=$(grep "^depends_on:" "$task_file" | head -1)
if [ -n "$deps_line" ]; then
deps=$(echo "$deps_line" | sed 's/^depends_on: *//' | sed 's/^\[//' | sed 's/\]$//' | sed 's/,/ /g' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
[ -z "$deps" ] && deps=""
else
deps=""
fi
if [ -n "$deps" ] && [ "$deps" != "depends_on:" ]; then
task_name=$(grep "^name:" "$task_file" | head -1 | sed 's/^name: *//')
task_num=$(basename "$task_file" .md)
echo "⏸️ Task #$task_num - $task_name"
echo " Epic: $epic_name"
echo " Blocked by: [$deps]"
# Check status of dependencies
open_deps=""
for dep in $deps; do
dep_file="$epic_dir$dep.md"
if [ -f "$dep_file" ]; then
dep_status=$(grep "^status:" "$dep_file" | head -1 | sed 's/^status: *//')
[ "$dep_status" = "open" ] && open_deps="$open_deps #$dep"
fi
done
[ -n "$open_deps" ] && echo " Waiting for:$open_deps"
echo ""
((found++))
fi
done
done
if [ $found -eq 0 ]; then
echo "No blocked tasks found!"
echo ""
echo "💡 All tasks with dependencies are either completed or in progress."
else
echo "📊 Total blocked: $found tasks"
fi
exit 0
@@ -0,0 +1,94 @@
#!/bin/bash
echo "Getting epics..."
echo ""
echo ""
[ ! -d ".claude/epics" ] && echo "📁 No epics directory found. Create your first epic with: /pm:prd-parse <feature-name>" && exit 0
[ -z "$(ls -d .claude/epics/*/ 2>/dev/null)" ] && echo "📁 No epics found. Create your first epic with: /pm:prd-parse <feature-name>" && exit 0
echo "📚 Project Epics"
echo "================"
echo ""
# Initialize arrays to store epics by status
planning_epics=""
in_progress_epics=""
completed_epics=""
# Process all epics
for dir in .claude/epics/*/; do
[ -d "$dir" ] || continue
[ -f "$dir/epic.md" ] || continue
# Extract metadata
n=$(grep "^name:" "$dir/epic.md" | head -1 | sed 's/^name: *//')
s=$(grep "^status:" "$dir/epic.md" | head -1 | sed 's/^status: *//' | tr '[:upper:]' '[:lower:]')
p=$(grep "^progress:" "$dir/epic.md" | head -1 | sed 's/^progress: *//')
g=$(grep "^github:" "$dir/epic.md" | head -1 | sed 's/^github: *//')
# Defaults
[ -z "$n" ] && n=$(basename "$dir")
[ -z "$p" ] && p="0%"
# Count tasks
t=$(ls "$dir"/[0-9]*.md 2>/dev/null | wc -l)
# Format output with GitHub issue number if available
if [ -n "$g" ]; then
i=$(echo "$g" | grep -o '/[0-9]*$' | tr -d '/')
entry=" 📋 ${dir}epic.md (#$i) - $p complete ($t tasks)"
else
entry=" 📋 ${dir}epic.md - $p complete ($t tasks)"
fi
# Categorize by status (handle various status values)
case "$s" in
planning|draft|"")
planning_epics="${planning_epics}${entry}\n"
;;
in-progress|in_progress|active|started)
in_progress_epics="${in_progress_epics}${entry}\n"
;;
completed|complete|done|closed|finished)
completed_epics="${completed_epics}${entry}\n"
;;
*)
# Default to planning for unknown statuses
planning_epics="${planning_epics}${entry}\n"
;;
esac
done
# Display categorized epics
echo "📝 Planning:"
if [ -n "$planning_epics" ]; then
echo -e "$planning_epics" | sed '/^$/d'
else
echo " (none)"
fi
echo ""
echo "🚀 In Progress:"
if [ -n "$in_progress_epics" ]; then
echo -e "$in_progress_epics" | sed '/^$/d'
else
echo " (none)"
fi
echo ""
echo "✅ Completed:"
if [ -n "$completed_epics" ]; then
echo -e "$completed_epics" | sed '/^$/d'
else
echo " (none)"
fi
# Summary
echo ""
echo "📊 Summary"
total=$(ls -d .claude/epics/*/ 2>/dev/null | wc -l)
tasks=$(find .claude/epics -name "[0-9]*.md" 2>/dev/null | wc -l)
echo " Total epics: $total"
echo " Total tasks: $tasks"
exit 0
@@ -0,0 +1,91 @@
#!/bin/bash
epic_name="$1"
if [ -z "$epic_name" ]; then
echo "❌ Please provide an epic name"
echo "Usage: /pm:epic-show <epic-name>"
exit 1
fi
echo "Getting epic..."
echo ""
echo ""
epic_dir=".claude/epics/$epic_name"
epic_file="$epic_dir/epic.md"
if [ ! -f "$epic_file" ]; then
echo "❌ Epic not found: $epic_name"
echo ""
echo "Available epics:"
for dir in .claude/epics/*/; do
[ -d "$dir" ] && echo "$(basename "$dir")"
done
exit 1
fi
# Display epic details
echo "📚 Epic: $epic_name"
echo "================================"
echo ""
# Extract metadata
status=$(grep "^status:" "$epic_file" | head -1 | sed 's/^status: *//')
progress=$(grep "^progress:" "$epic_file" | head -1 | sed 's/^progress: *//')
github=$(grep "^github:" "$epic_file" | head -1 | sed 's/^github: *//')
created=$(grep "^created:" "$epic_file" | head -1 | sed 's/^created: *//')
echo "📊 Metadata:"
echo " Status: ${status:-planning}"
echo " Progress: ${progress:-0%}"
[ -n "$github" ] && echo " GitHub: $github"
echo " Created: ${created:-unknown}"
echo ""
# Show tasks
echo "📝 Tasks:"
task_count=0
open_count=0
closed_count=0
for task_file in "$epic_dir"/[0-9]*.md; do
[ -f "$task_file" ] || continue
task_num=$(basename "$task_file" .md)
task_name=$(grep "^name:" "$task_file" | head -1 | sed 's/^name: *//')
task_status=$(grep "^status:" "$task_file" | head -1 | sed 's/^status: *//')
parallel=$(grep "^parallel:" "$task_file" | head -1 | sed 's/^parallel: *//')
if [ "$task_status" = "closed" ] || [ "$task_status" = "completed" ]; then
echo " ✅ #$task_num - $task_name"
((closed_count++))
else
echo " ⬜ #$task_num - $task_name"
[ "$parallel" = "true" ] && echo -n " (parallel)"
((open_count++))
fi
((task_count++))
done
if [ $task_count -eq 0 ]; then
echo " No tasks created yet"
echo " Run: /pm:epic-decompose $epic_name"
fi
echo ""
echo "📈 Statistics:"
echo " Total tasks: $task_count"
echo " Open: $open_count"
echo " Closed: $closed_count"
[ $task_count -gt 0 ] && echo " Completion: $((closed_count * 100 / task_count))%"
# Next actions
echo ""
echo "💡 Actions:"
[ $task_count -eq 0 ] && echo " • Decompose into tasks: /pm:epic-decompose $epic_name"
[ -z "$github" ] && [ $task_count -gt 0 ] && echo " • Sync to GitHub: /pm:epic-sync $epic_name"
[ -n "$github" ] && [ "$status" != "completed" ] && echo " • Start work: /pm:epic-start $epic_name"
exit 0
@@ -0,0 +1,90 @@
#!/bin/bash
echo "Getting status..."
echo ""
echo ""
epic_name="$1"
if [ -z "$epic_name" ]; then
echo "❌ Please specify an epic name"
echo "Usage: /pm:epic-status <epic-name>"
echo ""
echo "Available epics:"
for dir in .claude/epics/*/; do
[ -d "$dir" ] && echo "$(basename "$dir")"
done
exit 1
else
# Show status for specific epic
epic_dir=".claude/epics/$epic_name"
epic_file="$epic_dir/epic.md"
if [ ! -f "$epic_file" ]; then
echo "❌ Epic not found: $epic_name"
echo ""
echo "Available epics:"
for dir in .claude/epics/*/; do
[ -d "$dir" ] && echo "$(basename "$dir")"
done
exit 1
fi
echo "📚 Epic Status: $epic_name"
echo "================================"
echo ""
# Extract metadata
status=$(grep "^status:" "$epic_file" | head -1 | sed 's/^status: *//')
progress=$(grep "^progress:" "$epic_file" | head -1 | sed 's/^progress: *//')
github=$(grep "^github:" "$epic_file" | head -1 | sed 's/^github: *//')
# Count tasks
total=0
open=0
closed=0
blocked=0
# Use find to safely iterate over task files
for task_file in "$epic_dir"/[0-9]*.md; do
[ -f "$task_file" ] || continue
((total++))
task_status=$(grep "^status:" "$task_file" | head -1 | sed 's/^status: *//')
deps=$(grep "^depends_on:" "$task_file" | head -1 | sed 's/^depends_on: *\[//' | sed 's/\]//')
if [ "$task_status" = "closed" ] || [ "$task_status" = "completed" ]; then
((closed++))
elif [ -n "$deps" ] && [ "$deps" != "depends_on:" ]; then
((blocked++))
else
((open++))
fi
done
# Display progress bar
if [ $total -gt 0 ]; then
percent=$((closed * 100 / total))
filled=$((percent * 20 / 100))
empty=$((20 - filled))
echo -n "Progress: ["
[ $filled -gt 0 ] && printf '%0.s█' $(seq 1 $filled)
[ $empty -gt 0 ] && printf '%0.s░' $(seq 1 $empty)
echo "] $percent%"
else
echo "Progress: No tasks created"
fi
echo ""
echo "📊 Breakdown:"
echo " Total tasks: $total"
echo " ✅ Completed: $closed"
echo " 🔄 Available: $open"
echo " ⏸️ Blocked: $blocked"
[ -n "$github" ] && echo ""
[ -n "$github" ] && echo "🔗 GitHub: $github"
fi
exit 0
@@ -0,0 +1,71 @@
#!/bin/bash
echo "Helping..."
echo ""
echo ""
echo "📚 Claude Code PM - Project Management System"
echo "============================================="
echo ""
echo "🎯 Quick Start Workflow"
echo " 1. /pm:prd-new <name> - Create a new PRD"
echo " 2. /pm:prd-parse <name> - Convert PRD to epic"
echo " 3. /pm:epic-decompose <name> - Break into tasks"
echo " 4. /pm:epic-sync <name> - Push to GitHub"
echo " 5. /pm:epic-start <name> - Start parallel execution"
echo ""
echo "📄 PRD Commands"
echo " /pm:prd-new <name> - Launch brainstorming for new product requirement"
echo " /pm:prd-parse <name> - Convert PRD to implementation epic"
echo " /pm:prd-list - List all PRDs"
echo " /pm:prd-edit <name> - Edit existing PRD"
echo " /pm:prd-status - Show PRD implementation status"
echo ""
echo "📚 Epic Commands"
echo " /pm:epic-decompose <name> - Break epic into task files"
echo " /pm:epic-sync <name> - Push epic and tasks to GitHub"
echo " /pm:epic-oneshot <name> - Decompose and sync in one command"
echo " /pm:epic-list - List all epics"
echo " /pm:epic-show <name> - Display epic and its tasks"
echo " /pm:epic-status [name] - Show epic progress"
echo " /pm:epic-close <name> - Mark epic as complete"
echo " /pm:epic-edit <name> - Edit epic details"
echo " /pm:epic-refresh <name> - Update epic progress from tasks"
echo " /pm:epic-start <name> - Launch parallel agent execution"
echo ""
echo "📝 Issue Commands"
echo " /pm:issue-show <num> - Display issue and sub-issues"
echo " /pm:issue-status <num> - Check issue status"
echo " /pm:issue-start <num> - Begin work with specialized agent"
echo " /pm:issue-sync <num> - Push updates to GitHub"
echo " /pm:issue-close <num> - Mark issue as complete"
echo " /pm:issue-reopen <num> - Reopen closed issue"
echo " /pm:issue-edit <num> - Edit issue details"
echo " /pm:issue-analyze <num> - Analyze for parallel work streams"
echo ""
echo "🔄 Workflow Commands"
echo " /pm:next - Show next priority tasks"
echo " /pm:status - Overall project dashboard"
echo " /pm:standup - Daily standup report"
echo " /pm:blocked - Show blocked tasks"
echo " /pm:in-progress - List work in progress"
echo ""
echo "🔗 Sync Commands"
echo " /pm:sync - Full bidirectional sync with GitHub"
echo " /pm:import <issue> - Import existing GitHub issues"
echo ""
echo "🔧 Maintenance Commands"
echo " /pm:validate - Check system integrity"
echo " /pm:clean - Archive completed work"
echo " /pm:search <query> - Search across all content"
echo ""
echo "⚙️ Setup Commands"
echo " /pm:init - Install dependencies and configure GitHub"
echo " /pm:help - Show this help message"
echo ""
echo "💡 Tips"
echo " • Use /pm:next to find available work"
echo " • Run /pm:status for quick overview"
echo " • Epic workflow: prd-new → prd-parse → epic-decompose → epic-sync"
echo " • View README.md for complete documentation"
exit 0
@@ -0,0 +1,74 @@
#!/bin/bash
echo "Getting status..."
echo ""
echo ""
echo "🔄 In Progress Work"
echo "==================="
echo ""
# Check for active work in updates directories
found=0
if [ -d ".claude/epics" ]; then
for updates_dir in .claude/epics/*/updates/*/; do
[ -d "$updates_dir" ] || continue
issue_num=$(basename "$updates_dir")
epic_name=$(basename $(dirname $(dirname "$updates_dir")))
if [ -f "$updates_dir/progress.md" ]; then
completion=$(grep "^completion:" "$updates_dir/progress.md" | head -1 | sed 's/^completion: *//')
[ -z "$completion" ] && completion="0%"
# Get task name from the task file
task_file=".claude/epics/$epic_name/$issue_num.md"
if [ -f "$task_file" ]; then
task_name=$(grep "^name:" "$task_file" | head -1 | sed 's/^name: *//')
else
task_name="Unknown task"
fi
echo "📝 Issue #$issue_num - $task_name"
echo " Epic: $epic_name"
echo " Progress: $completion complete"
# Check for recent updates
if [ -f "$updates_dir/progress.md" ]; then
last_update=$(grep "^last_sync:" "$updates_dir/progress.md" | head -1 | sed 's/^last_sync: *//')
[ -n "$last_update" ] && echo " Last update: $last_update"
fi
echo ""
((found++))
fi
done
fi
# Also check for in-progress epics
echo "📚 Active Epics:"
for epic_dir in .claude/epics/*/; do
[ -d "$epic_dir" ] || continue
[ -f "$epic_dir/epic.md" ] || continue
status=$(grep "^status:" "$epic_dir/epic.md" | head -1 | sed 's/^status: *//')
if [ "$status" = "in-progress" ] || [ "$status" = "active" ]; then
epic_name=$(grep "^name:" "$epic_dir/epic.md" | head -1 | sed 's/^name: *//')
progress=$(grep "^progress:" "$epic_dir/epic.md" | head -1 | sed 's/^progress: *//')
[ -z "$epic_name" ] && epic_name=$(basename "$epic_dir")
[ -z "$progress" ] && progress="0%"
echo "$epic_name - $progress complete"
fi
done
echo ""
if [ $found -eq 0 ]; then
echo "No active work items found."
echo ""
echo "💡 Start work with: /pm:next"
else
echo "📊 Total active items: $found"
fi
exit 0
@@ -0,0 +1,192 @@
#!/bin/bash
echo "Initializing..."
echo ""
echo ""
echo " ██████╗ ██████╗██████╗ ███╗ ███╗"
echo "██╔════╝██╔════╝██╔══██╗████╗ ████║"
echo "██║ ██║ ██████╔╝██╔████╔██║"
echo "╚██████╗╚██████╗██║ ██║ ╚═╝ ██║"
echo " ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚═╝"
echo "┌─────────────────────────────────┐"
echo "│ Claude Code Project Management │"
echo "│ by https://x.com/aroussi │"
echo "└─────────────────────────────────┘"
echo "https://github.com/automazeio/ccpm"
echo ""
echo ""
echo "🚀 Initializing Claude Code PM System"
echo "======================================"
echo ""
# Check for required tools
echo "🔍 Checking dependencies..."
# Check gh CLI
if command -v gh &> /dev/null; then
echo " ✅ GitHub CLI (gh) installed"
else
echo " ❌ GitHub CLI (gh) not found"
echo ""
echo " Installing gh..."
if command -v brew &> /dev/null; then
brew install gh
elif command -v apt-get &> /dev/null; then
sudo apt-get update && sudo apt-get install gh
else
echo " Please install GitHub CLI manually: https://cli.github.com/"
exit 1
fi
fi
# Check gh auth status
echo ""
echo "🔐 Checking GitHub authentication..."
if gh auth status &> /dev/null; then
echo " ✅ GitHub authenticated"
else
echo " ⚠️ GitHub not authenticated"
echo " Running: gh auth login"
gh auth login
fi
# Check for gh-sub-issue extension
echo ""
echo "📦 Checking gh extensions..."
if gh extension list | grep -q "yahsan2/gh-sub-issue"; then
echo " ✅ gh-sub-issue extension installed"
else
echo " 📥 Installing gh-sub-issue extension..."
gh extension install yahsan2/gh-sub-issue
fi
# Create directory structure
echo ""
echo "📁 Creating directory structure..."
mkdir -p .claude/prds
mkdir -p .claude/epics
mkdir -p .claude/rules
mkdir -p .claude/agents
mkdir -p .claude/scripts/pm
echo " ✅ Directories created"
# Copy scripts if in main repo
if [ -d "scripts/pm" ] && [ ! "$(pwd)" = *"/.claude"* ]; then
echo ""
echo "📝 Copying PM scripts..."
cp -r scripts/pm/* .claude/scripts/pm/
chmod +x .claude/scripts/pm/*.sh
echo " ✅ Scripts copied and made executable"
fi
# Check for git
echo ""
echo "🔗 Checking Git configuration..."
if git rev-parse --git-dir > /dev/null 2>&1; then
echo " ✅ Git repository detected"
# Check remote
if git remote -v | grep -q origin; then
remote_url=$(git remote get-url origin)
echo " ✅ Remote configured: $remote_url"
# Check if remote is the CCPM template repository
if [[ "$remote_url" == *"automazeio/ccpm"* ]] || [[ "$remote_url" == *"automazeio/ccpm.git"* ]]; then
echo ""
echo " ⚠️ WARNING: Your remote origin points to the CCPM template repository!"
echo " This means any issues you create will go to the template repo, not your project."
echo ""
echo " To fix this:"
echo " 1. Fork the repository or create your own on GitHub"
echo " 2. Update your remote:"
echo " git remote set-url origin https://github.com/YOUR_USERNAME/YOUR_REPO.git"
echo ""
else
# Create GitHub labels if this is a GitHub repository
if gh repo view &> /dev/null; then
echo ""
echo "🏷️ Creating GitHub labels..."
# Create base labels with improved error handling
epic_created=false
task_created=false
if gh label create "epic" --color "0E8A16" --description "Epic issue containing multiple related tasks" --force 2>/dev/null; then
epic_created=true
elif gh label list 2>/dev/null | grep -q "^epic"; then
epic_created=true # Label already exists
fi
if gh label create "task" --color "1D76DB" --description "Individual task within an epic" --force 2>/dev/null; then
task_created=true
elif gh label list 2>/dev/null | grep -q "^task"; then
task_created=true # Label already exists
fi
# Report results
if $epic_created && $task_created; then
echo " ✅ GitHub labels created (epic, task)"
elif $epic_created || $task_created; then
echo " ⚠️ Some GitHub labels created (epic: $epic_created, task: $task_created)"
else
echo " ❌ Could not create GitHub labels (check repository permissions)"
fi
else
echo " ️ Not a GitHub repository - skipping label creation"
fi
fi
else
echo " ⚠️ No remote configured"
echo " Add with: git remote add origin <url>"
fi
else
echo " ⚠️ Not a git repository"
echo " Initialize with: git init"
fi
# Create CLAUDE.md if it doesn't exist
if [ ! -f "CLAUDE.md" ]; then
echo ""
echo "📄 Creating CLAUDE.md..."
cat > CLAUDE.md << 'EOF'
# CLAUDE.md
> Think carefully and implement the most concise solution that changes as little code as possible.
## Project-Specific Instructions
Add your project-specific instructions here.
## Testing
Always run tests before committing:
- `npm test` or equivalent for your stack
## Code Style
Follow existing patterns in the codebase.
EOF
echo " ✅ CLAUDE.md created"
fi
# Summary
echo ""
echo "✅ Initialization Complete!"
echo "=========================="
echo ""
echo "📊 System Status:"
gh --version | head -1
echo " Extensions: $(gh extension list | wc -l) installed"
echo " Auth: $(gh auth status 2>&1 | grep -o 'Logged in to [^ ]*' || echo 'Not authenticated')"
echo ""
echo "🎯 Next Steps:"
echo " 1. Create your first PRD: /pm:prd-new <feature-name>"
echo " 2. View help: /pm:help"
echo " 3. Check status: /pm:status"
echo ""
echo "📚 Documentation: README.md"
exit 0
@@ -0,0 +1,61 @@
#!/bin/bash
echo "Getting status..."
echo ""
echo ""
echo "📋 Next Available Tasks"
echo "======================="
echo ""
# Find tasks that are open and have no dependencies or whose dependencies are closed
found=0
for epic_dir in .claude/epics/*/; do
[ -d "$epic_dir" ] || continue
epic_name=$(basename "$epic_dir")
for task_file in "$epic_dir"/[0-9]*.md; do
[ -f "$task_file" ] || continue
# Check if task is open
status=$(grep "^status:" "$task_file" | head -1 | sed 's/^status: *//')
if [ "$status" != "open" ] && [ -n "$status" ]; then
continue
fi
# Check dependencies
deps_line=$(grep "^depends_on:" "$task_file" | head -1)
if [ -n "$deps_line" ]; then
deps=$(echo "$deps_line" | sed 's/^depends_on: *//' | sed 's/^\[//' | sed 's/\]$//' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
[ -z "$deps" ] && deps=""
else
deps=""
fi
# If no dependencies or empty, task is available
if [ -z "$deps" ] || [ "$deps" = "depends_on:" ]; then
task_name=$(grep "^name:" "$task_file" | head -1 | sed 's/^name: *//')
task_num=$(basename "$task_file" .md)
parallel=$(grep "^parallel:" "$task_file" | head -1 | sed 's/^parallel: *//')
echo "✅ Ready: #$task_num - $task_name"
echo " Epic: $epic_name"
[ "$parallel" = "true" ] && echo " 🔄 Can run in parallel"
echo ""
((found++))
fi
done
done
if [ $found -eq 0 ]; then
echo "No available tasks found."
echo ""
echo "💡 Suggestions:"
echo " • Check blocked tasks: /pm:blocked"
echo " • View all tasks: /pm:epic-list"
fi
echo ""
echo "📊 Summary: $found tasks ready to start"
exit 0
@@ -0,0 +1,89 @@
# !/bin/bash
# Check if PRD directory exists
if [ ! -d ".claude/prds" ]; then
echo "📁 No PRD directory found. Create your first PRD with: /pm:prd-new <feature-name>"
exit 0
fi
# Check for PRD files
if ! ls .claude/prds/*.md >/dev/null 2>&1; then
echo "📁 No PRDs found. Create your first PRD with: /pm:prd-new <feature-name>"
exit 0
fi
# Initialize counters
backlog_count=0
in_progress_count=0
implemented_count=0
total_count=0
echo "Getting PRDs..."
echo ""
echo ""
echo "📋 PRD List"
echo "==========="
echo ""
# Display by status groups
echo "🔍 Backlog PRDs:"
for file in .claude/prds/*.md; do
[ -f "$file" ] || continue
status=$(grep "^status:" "$file" | head -1 | sed 's/^status: *//')
if [ "$status" = "backlog" ] || [ "$status" = "draft" ] || [ -z "$status" ]; then
name=$(grep "^name:" "$file" | head -1 | sed 's/^name: *//')
desc=$(grep "^description:" "$file" | head -1 | sed 's/^description: *//')
[ -z "$name" ] && name=$(basename "$file" .md)
[ -z "$desc" ] && desc="No description"
# echo " 📋 $name - $desc"
echo " 📋 $file - $desc"
((backlog_count++))
fi
((total_count++))
done
[ $backlog_count -eq 0 ] && echo " (none)"
echo ""
echo "🔄 In-Progress PRDs:"
for file in .claude/prds/*.md; do
[ -f "$file" ] || continue
status=$(grep "^status:" "$file" | head -1 | sed 's/^status: *//')
if [ "$status" = "in-progress" ] || [ "$status" = "active" ]; then
name=$(grep "^name:" "$file" | head -1 | sed 's/^name: *//')
desc=$(grep "^description:" "$file" | head -1 | sed 's/^description: *//')
[ -z "$name" ] && name=$(basename "$file" .md)
[ -z "$desc" ] && desc="No description"
# echo " 📋 $name - $desc"
echo " 📋 $file - $desc"
((in_progress_count++))
fi
done
[ $in_progress_count -eq 0 ] && echo " (none)"
echo ""
echo "✅ Implemented PRDs:"
for file in .claude/prds/*.md; do
[ -f "$file" ] || continue
status=$(grep "^status:" "$file" | head -1 | sed 's/^status: *//')
if [ "$status" = "implemented" ] || [ "$status" = "completed" ] || [ "$status" = "done" ]; then
name=$(grep "^name:" "$file" | head -1 | sed 's/^name: *//')
desc=$(grep "^description:" "$file" | head -1 | sed 's/^description: *//')
[ -z "$name" ] && name=$(basename "$file" .md)
[ -z "$desc" ] && desc="No description"
# echo " 📋 $name - $desc"
echo " 📋 $file - $desc"
((implemented_count++))
fi
done
[ $implemented_count -eq 0 ] && echo " (none)"
# Display summary
echo ""
echo "📊 PRD Summary"
echo " Total PRDs: $total_count"
echo " Backlog: $backlog_count"
echo " In-Progress: $in_progress_count"
echo " Implemented: $implemented_count"
exit 0
@@ -0,0 +1,63 @@
#!/bin/bash
echo "📄 PRD Status Report"
echo "===================="
echo ""
if [ ! -d ".claude/prds" ]; then
echo "No PRD directory found."
exit 0
fi
total=$(ls .claude/prds/*.md 2>/dev/null | wc -l)
[ $total -eq 0 ] && echo "No PRDs found." && exit 0
# Count by status
backlog=0
in_progress=0
implemented=0
for file in .claude/prds/*.md; do
[ -f "$file" ] || continue
status=$(grep "^status:" "$file" | head -1 | sed 's/^status: *//')
case "$status" in
backlog|draft|"") ((backlog++)) ;;
in-progress|active) ((in_progress++)) ;;
implemented|completed|done) ((implemented++)) ;;
*) ((backlog++)) ;;
esac
done
echo "Getting status..."
echo ""
echo ""
# Display chart
echo "📊 Distribution:"
echo "================"
echo ""
echo " Backlog: $(printf '%-3d' $backlog) [$(printf '%0.s█' $(seq 1 $((backlog*20/total))))]"
echo " In Progress: $(printf '%-3d' $in_progress) [$(printf '%0.s█' $(seq 1 $((in_progress*20/total))))]"
echo " Implemented: $(printf '%-3d' $implemented) [$(printf '%0.s█' $(seq 1 $((implemented*20/total))))]"
echo ""
echo " Total PRDs: $total"
# Recent activity
echo ""
echo "📅 Recent PRDs (last 5 modified):"
ls -t .claude/prds/*.md 2>/dev/null | head -5 | while read file; do
name=$(grep "^name:" "$file" | head -1 | sed 's/^name: *//')
[ -z "$name" ] && name=$(basename "$file" .md)
echo "$name"
done
# Suggestions
echo ""
echo "💡 Next Actions:"
[ $backlog -gt 0 ] && echo " • Parse backlog PRDs to epics: /pm:prd-parse <name>"
[ $in_progress -gt 0 ] && echo " • Check progress on active PRDs: /pm:epic-status <name>"
[ $total -eq 0 ] && echo " • Create your first PRD: /pm:prd-new <name>"
exit 0
@@ -0,0 +1,71 @@
#!/bin/bash
query="$1"
if [ -z "$query" ]; then
echo "❌ Please provide a search query"
echo "Usage: /pm:search <query>"
exit 1
fi
echo "Searching for '$query'..."
echo ""
echo ""
echo "🔍 Search results for: '$query'"
echo "================================"
echo ""
# Search in PRDs
if [ -d ".claude/prds" ]; then
echo "📄 PRDs:"
results=$(grep -l -i "$query" .claude/prds/*.md 2>/dev/null)
if [ -n "$results" ]; then
for file in $results; do
name=$(basename "$file" .md)
matches=$(grep -c -i "$query" "$file")
echo "$name ($matches matches)"
done
else
echo " No matches"
fi
echo ""
fi
# Search in Epics
if [ -d ".claude/epics" ]; then
echo "📚 Epics:"
results=$(find .claude/epics -name "epic.md" -exec grep -l -i "$query" {} \; 2>/dev/null)
if [ -n "$results" ]; then
for file in $results; do
epic_name=$(basename $(dirname "$file"))
matches=$(grep -c -i "$query" "$file")
echo "$epic_name ($matches matches)"
done
else
echo " No matches"
fi
echo ""
fi
# Search in Tasks
if [ -d ".claude/epics" ]; then
echo "📝 Tasks:"
results=$(find .claude/epics -name "[0-9]*.md" -exec grep -l -i "$query" {} \; 2>/dev/null | head -10)
if [ -n "$results" ]; then
for file in $results; do
epic_name=$(basename $(dirname "$file"))
task_num=$(basename "$file" .md)
echo " • Task #$task_num in $epic_name"
done
else
echo " No matches"
fi
fi
# Summary
total=$(find .claude -name "*.md" -exec grep -l -i "$query" {} \; 2>/dev/null | wc -l)
echo ""
echo "📊 Total files with matches: $total"
exit 0
@@ -0,0 +1,86 @@
#!/bin/bash
echo "📅 Daily Standup - $(date '+%Y-%m-%d')"
echo "================================"
echo ""
today=$(date '+%Y-%m-%d')
echo "Getting status..."
echo ""
echo ""
echo "📝 Today's Activity:"
echo "===================="
echo ""
# Find files modified today
recent_files=$(find .claude -name "*.md" -mtime -1 2>/dev/null)
if [ -n "$recent_files" ]; then
# Count by type
prd_count=$(echo "$recent_files" | grep -c "/prds/" 2>/dev/null | tr -d '[:space:]')
epic_count=$(echo "$recent_files" | grep -c "/epic.md" 2>/dev/null | tr -d '[:space:]')
task_count=$(echo "$recent_files" | grep -c "/[0-9]*.md" 2>/dev/null | tr -d '[:space:]')
update_count=$(echo "$recent_files" | grep -c "/updates/" 2>/dev/null | tr -d '[:space:]')
prd_count=${prd_count:-0}; epic_count=${epic_count:-0}; task_count=${task_count:-0}; update_count=${update_count:-0}
[ "$prd_count" -gt 0 ] && echo " • Modified $prd_count PRD(s)"
[ "$epic_count" -gt 0 ] && echo " • Updated $epic_count epic(s)"
[ "$task_count" -gt 0 ] && echo " • Worked on $task_count task(s)"
[ "$update_count" -gt 0 ] && echo " • Posted $update_count progress update(s)"
else
echo " No activity recorded today"
fi
echo ""
echo "🔄 Currently In Progress:"
# Show active work items
for updates_dir in .claude/epics/*/updates/*/; do
[ -d "$updates_dir" ] || continue
if [ -f "$updates_dir/progress.md" ]; then
issue_num=$(basename "$updates_dir")
epic_name=$(basename $(dirname $(dirname "$updates_dir")))
completion=$(grep "^completion:" "$updates_dir/progress.md" | head -1 | sed 's/^completion: *//')
echo " • Issue #$issue_num ($epic_name) - ${completion:-0%} complete"
fi
done
echo ""
echo "⏭️ Next Available Tasks:"
# Show top 3 available tasks
count=0
for epic_dir in .claude/epics/*/; do
[ -d "$epic_dir" ] || continue
for task_file in "$epic_dir"/[0-9]*.md; do
[ -f "$task_file" ] || continue
status=$(grep "^status:" "$task_file" | head -1 | sed 's/^status: *//')
if [ "$status" != "open" ] && [ -n "$status" ]; then
continue
fi
deps_line=$(grep "^depends_on:" "$task_file" | head -1)
if [ -n "$deps_line" ]; then
deps=$(echo "$deps_line" | sed 's/^depends_on: *//' | sed 's/^\[//' | sed 's/\]$//' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
[ -z "$deps" ] && deps=""
else
deps=""
fi
if [ -z "$deps" ] || [ "$deps" = "depends_on:" ]; then
task_name=$(grep "^name:" "$task_file" | head -1 | sed 's/^name: *//')
task_num=$(basename "$task_file" .md)
echo " • #$task_num - $task_name"
((count++))
[ $count -ge 3 ] && break 2
fi
done
done
echo ""
echo "📊 Quick Stats:"
total_tasks=$(find .claude/epics -name "[0-9]*.md" 2>/dev/null | wc -l)
open_tasks=$(find .claude/epics -name "[0-9]*.md" -exec grep -l "^status: *open" {} \; 2>/dev/null | wc -l)
closed_tasks=$(find .claude/epics -name "[0-9]*.md" -exec grep -l "^status: *closed" {} \; 2>/dev/null | wc -l)
echo " Tasks: $open_tasks open, $closed_tasks closed, $total_tasks total"
exit 0
@@ -0,0 +1,42 @@
#!/bin/bash
echo "Getting status..."
echo ""
echo ""
echo "📊 Project Status"
echo "================"
echo ""
echo "📄 PRDs:"
if [ -d ".claude/prds" ]; then
total=$(ls .claude/prds/*.md 2>/dev/null | wc -l)
echo " Total: $total"
else
echo " No PRDs found"
fi
echo ""
echo "📚 Epics:"
if [ -d ".claude/epics" ]; then
total=$(ls -d .claude/epics/*/ 2>/dev/null | grep -v '/archived/$' | wc -l)
echo " Total: $total"
else
echo " No epics found"
fi
echo ""
echo "📝 Tasks:"
if [ -d ".claude/epics" ]; then
total=$(find .claude/epics -path "*/archived/*" -prune -o -name "[0-9]*.md" -print 2>/dev/null | wc -l)
open=$(find .claude/epics -path "*/archived/*" -prune -o -name "[0-9]*.md" -print 2>/dev/null | xargs grep -l "^status: *open" 2>/dev/null | wc -l)
closed=$(find .claude/epics -path "*/archived/*" -prune -o -name "[0-9]*.md" -print 2>/dev/null | xargs grep -l "^status: *closed" 2>/dev/null | wc -l)
echo " Open: $open"
echo " Closed: $closed"
echo " Total: $total"
else
echo " No tasks found"
fi
exit 0
@@ -0,0 +1,96 @@
#!/bin/bash
echo "Validating PM System..."
echo ""
echo ""
echo "🔍 Validating PM System"
echo "======================="
echo ""
errors=0
warnings=0
# Check directory structure
echo "📁 Directory Structure:"
[ -d ".claude" ] && echo " ✅ .claude directory exists" || { echo " ❌ .claude directory missing"; ((errors++)); }
[ -d ".claude/prds" ] && echo " ✅ PRDs directory exists" || echo " ⚠️ PRDs directory missing"
[ -d ".claude/epics" ] && echo " ✅ Epics directory exists" || echo " ⚠️ Epics directory missing"
[ -d ".claude/rules" ] && echo " ✅ Rules directory exists" || echo " ⚠️ Rules directory missing"
echo ""
# Check for orphaned files
echo "🗂️ Data Integrity:"
# Check epics have epic.md files
for epic_dir in .claude/epics/*/; do
[ -d "$epic_dir" ] || continue
if [ ! -f "$epic_dir/epic.md" ]; then
echo " ⚠️ Missing epic.md in $(basename "$epic_dir")"
((warnings++))
fi
done
# Check for tasks without epics
orphaned=$(find .claude -name "[0-9]*.md" -not -path ".claude/epics/*/*" 2>/dev/null | wc -l)
[ $orphaned -gt 0 ] && echo " ⚠️ Found $orphaned orphaned task files" && ((warnings++))
# Check for broken references
echo ""
echo "🔗 Reference Check:"
for task_file in .claude/epics/*/[0-9]*.md; do
[ -f "$task_file" ] || continue
deps_line=$(grep "^depends_on:" "$task_file" | head -1)
if [ -n "$deps_line" ]; then
deps=$(echo "$deps_line" | sed 's/^depends_on: *//' | sed 's/^\[//' | sed 's/\]$//' | sed 's/,/ /g' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
[ -z "$deps" ] && deps=""
else
deps=""
fi
if [ -n "$deps" ] && [ "$deps" != "depends_on:" ]; then
epic_dir=$(dirname "$task_file")
for dep in $deps; do
if [ ! -f "$epic_dir/$dep.md" ]; then
echo " ⚠️ Task $(basename "$task_file" .md) references missing task: $dep"
((warnings++))
fi
done
fi
done
if [ $warnings -eq 0 ] && [ $errors -eq 0 ]; then
echo " ✅ All references valid"
fi
# Check frontmatter
echo ""
echo "📝 Frontmatter Validation:"
invalid=0
for file in $(find .claude -name "*.md" -path "*/epics/*" -o -path "*/prds/*" 2>/dev/null); do
if ! grep -q "^---" "$file"; then
echo " ⚠️ Missing frontmatter: $(basename "$file")"
((invalid++))
fi
done
[ $invalid -eq 0 ] && echo " ✅ All files have frontmatter"
# Summary
echo ""
echo "📊 Validation Summary:"
echo " Errors: $errors"
echo " Warnings: $warnings"
echo " Invalid files: $invalid"
if [ $errors -eq 0 ] && [ $warnings -eq 0 ] && [ $invalid -eq 0 ]; then
echo ""
echo "✅ System is healthy!"
else
echo ""
echo "💡 Run /pm:clean to fix some issues automatically"
fi
exit 0
+111
View File
@@ -0,0 +1,111 @@
# Structure — Break Down an Epic
This phase converts a technical epic into concrete, numbered task files with dependency and parallelization metadata.
---
## Epic Decomposition
**Trigger**: User wants to break an epic into actionable tasks.
### Preflight
- Verify `.claude/epics/<name>/epic.md` exists with valid frontmatter.
- If numbered task files (001.md, 002.md...) already exist in the epic directory, list them and confirm deletion before recreating.
- If epic status is "completed", warn the user before proceeding.
### Process
Read the epic fully. Analyze for parallelism — which pieces of work can happen simultaneously without file conflicts?
**Task types to consider:**
- Setup: environment, scaffolding, dependencies
- Data: models, schemas, migrations
- API: endpoints, services, integration
- UI: components, pages, styling
- Tests: unit, integration, e2e
- Docs: README, API docs, changelogs
**Parallelization strategy by epic size:**
- Small (<5 tasks): create sequentially
- Medium (510 tasks): batch into 23 groups, spawn parallel Task agents
- Large (>10 tasks): analyze dependencies first, launch parallel agents (max 5 concurrent), create dependent tasks after prerequisites
For parallel creation, use the Task tool:
```yaml
Task:
description: "Create task files batch N"
subagent_type: "general-purpose"
prompt: |
Create task files for epic: <name>
Tasks to create: [list 3-4 tasks]
Save to: .claude/epics/<name>/001.md, 002.md, etc.
Follow the task file format exactly.
Return: list of files created.
```
### Task File Format
```markdown
---
name: <Task Title>
status: open
created: <run: date -u +"%Y-%m-%dT%H:%M:%SZ">
updated: <same as created>
github: (will be set on sync)
depends_on: []
parallel: true
conflicts_with: []
---
# Task: <Task Title>
## Description
## Acceptance Criteria
- [ ]
## Technical Details
## Dependencies
## Effort Estimate
- Size: XS/S/M/L/XL
- Hours: N
## Definition of Done
- [ ] Code implemented
- [ ] Tests written and passing
- [ ] Code reviewed
```
**Numbering**: sequential 001.md, 002.md, etc. Tasks are renamed to GitHub issue numbers after sync — do not hard-code dependencies by filename, use the `depends_on` array.
### After Creating All Tasks
Append a summary to the epic file:
```markdown
## Tasks Created
- [ ] 001.md - <Title> (parallel: true/false)
- [ ] 002.md - <Title> (parallel: true/false)
Total tasks: N
Parallel tasks: N
Sequential tasks: N
Estimated total effort: N hours
```
**After completion**: Confirm "✅ Created N tasks for epic: <name>" and suggest: "Ready to push to GitHub? Say: sync the <name> epic"
---
## Dependency Rules
- `depends_on` lists task numbers that must complete before this task can start.
- `parallel: true` means the task can run concurrently with others it doesn't conflict with.
- `conflicts_with` lists tasks that touch the same files — these cannot run in parallel.
- Circular dependencies are an error — check before finalizing.
+315
View File
@@ -0,0 +1,315 @@
# Sync — Push to GitHub & Track Progress
This phase covers pushing local epics/tasks to GitHub as issues, syncing progress as comments, and closing issues when work is done.
---
## Repository Safety Check
**Always run this before any GitHub write operation:**
```bash
remote_url=$(git remote get-url origin 2>/dev/null || echo "")
if [[ "$remote_url" == *"automazeio/ccpm"* ]]; then
echo "❌ Cannot sync to the CCPM template repository."
echo "Update remote: git remote set-url origin https://github.com/YOUR/REPO.git"
exit 1
fi
REPO=$(echo "$remote_url" | sed 's|.*github.com[:/]||' | sed 's|\.git$||')
```
---
## Epic Sync — Push Epic + Tasks to GitHub
**Trigger**: User wants to push a local epic and its tasks to GitHub as issues.
### Preflight
- Verify `.claude/epics/<name>/epic.md` exists.
- Verify numbered task files exist — if none: "❌ No tasks to sync. Decompose the epic first."
### Process
**Step 1 — Create epic issue:**
Strip frontmatter from epic.md, then:
```bash
sed '1,/^---$/d; 1,/^---$/d' .claude/epics/<name>/epic.md > /tmp/epic-body.md
epic_number=$(gh issue create \
--repo "$REPO" \
--title "Epic: <name>" \
--body-file /tmp/epic-body.md \
--label "epic,epic:<name>,feature" \
--json number -q .number)
```
**Step 2 — Create task sub-issues:**
Check if `gh-sub-issue` extension is available:
```bash
if gh extension list | grep -q "yahsan2/gh-sub-issue"; then
use_subissues=true
fi
```
For <5 tasks: create sequentially.
For ≥5 tasks: use parallel Task agents (3-4 tasks per batch).
Per task:
```bash
sed '1,/^---$/d; 1,/^---$/d' <task_file> > /tmp/task-body.md
task_number=$(gh issue create \
--repo "$REPO" \
--title "<task_name>" \
--body-file /tmp/task-body.md \
--label "task,epic:<name>" \
--json number -q .number)
# or with sub-issues:
# gh sub-issue create --parent $epic_number ...
```
**Step 3 — Rename task files and update references:**
After all issues are created, rename `001.md``<issue_number>.md` and update all `depends_on`/`conflicts_with` arrays to use real issue numbers (not sequential numbers).
```bash
# Build old→new mapping, then for each task file:
sed -i.bak "s/\b001\b/<new_num_1>/g" <file> # repeat for each mapping
mv 001.md <new_num>.md
```
**Step 4 — Update frontmatter:**
```bash
current_date=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Update github: and updated: fields in epic.md and each task file
github_url="https://github.com/$REPO/issues/<number>"
sed -i.bak "/^github:/c\\github: $github_url" <file>
sed -i.bak "/^updated:/c\\updated: $current_date" <file>
rm <file>.bak
```
**Step 5 — Create worktree for the epic:**
```bash
git checkout main && git pull origin main
git worktree add ../epic-<name> -b epic/<name>
```
**Step 6 — Create github-mapping.md:**
```markdown
# GitHub Issue Mapping
Epic: #<N> - https://github.com/<repo>/issues/<N>
Tasks:
- #<N>: <title> - https://github.com/<repo>/issues/<N>
Synced: <datetime>
```
**Output:**
```
✅ Synced epic <name> to GitHub
Epic: #<N>
Tasks: N sub-issues
Worktree: ../epic-<name>
Next: "start working on issue <N>" or "start the <name> epic"
```
---
## Issue Sync — Post Progress to GitHub
**Trigger**: User wants to sync local development progress to a GitHub issue as a comment.
### Preflight
- Verify issue exists: `gh issue view <N> --json state`
- Check `.claude/epics/*/updates/<N>/` exists with a `progress.md` file.
- Check `last_sync` in progress.md — if synced <5 minutes ago, confirm before proceeding.
### Process
Gather updates from `.claude/epics/<epic>/updates/<N>/` (progress.md, notes.md, commits.md).
Format and post a comment:
```bash
gh issue comment <N> --body-file /tmp/update-comment.md
```
Comment format:
```markdown
## 🔄 Progress Update - <date>
### ✅ Completed Work
### 🔄 In Progress
### 📝 Technical Notes
### 📊 Acceptance Criteria Status
### 🚀 Next Steps
### ⚠️ Blockers
---
*Progress: N% | Synced at <timestamp>*
```
After posting: update `last_sync` in progress.md frontmatter, update `updated` in the task file.
Add sync marker to local files to prevent duplicate comments:
```markdown
<!-- SYNCED: <datetime> -->
```
---
## Closing an Issue
**Trigger**: User marks a task complete.
### Process
1. Find the local task file (`.claude/epics/*/<N>.md`).
2. Update frontmatter: `status: closed`, `updated: <now>`.
3. Post completion comment:
```bash
echo "✅ Task completed — all acceptance criteria met." | gh issue comment <N> --body-file -
gh issue close <N>
```
1. Check off the task in the epic issue body:
```bash
gh issue view <epic_N> --json body -q .body > /tmp/epic-body.md
sed -i "s/- \[ \] #<N>/- [x] #<N>/" /tmp/epic-body.md
gh issue edit <epic_N> --body-file /tmp/epic-body.md
```
1. Recalculate and update epic progress: `progress = closed_tasks / total_tasks * 100`
---
## Merging an Epic
**Trigger**: User wants to merge a completed epic back to main.
### Preflight
- Verify worktree `../epic-<name>` exists.
- Check for uncommitted changes in the worktree — block if dirty.
- Warn if any task issues are still open.
### Process
```bash
# From worktree: run project tests if detectable
cd ../epic-<name>
# detect and run: npm test / pytest / cargo test / go test / etc.
# From main repo:
git checkout main && git pull origin main
git merge epic/<name> --no-ff -m "Merge epic: <name>"
git push origin main
# Cleanup
git worktree remove ../epic-<name>
git branch -d epic/<name>
git push origin --delete epic/<name>
# Archive
mkdir -p .claude/epics/archived/
mv .claude/epics/<name> .claude/epics/archived/
# Close GitHub issues
epic_issue=$(grep 'github:' .claude/epics/archived/<name>/epic.md | grep -oE '[0-9]+$')
gh issue close $epic_issue -c "Epic completed and merged to main"
```
Update epic.md frontmatter: `status: completed`.
---
## Reporting a Bug Against a Completed Issue
**Trigger**: User finds a bug while testing a completed or in-progress issue — e.g. "found a bug in issue 42", "email validation is broken, came up while testing issue 42".
The workflow should stay automated: create a linked bug task without losing context from the original issue.
### Process
**Step 1 — Read the original issue for context:**
```bash
gh issue view <original_N> --json title,body,labels
```
Also read the local task file if it exists: `.claude/epics/*/<original_N>.md`
**Step 2 — Create a local bug task file:**
```markdown
---
name: Bug: <short description>
status: open
created: <run: date -u +"%Y-%m-%dT%H:%M:%SZ">
updated: <same>
github: (will be set on sync)
depends_on: []
parallel: false
conflicts_with: []
bug_for: <original_N>
---
# Bug: <short description>
## Context
Found while working on / testing issue #<original_N>: <original title>
## Description
<what's broken>
## Steps to Reproduce
<steps>
## Expected vs Actual
- Expected:
- Actual:
## Acceptance Criteria
- [ ] Bug is fixed
- [ ] Original issue #<original_N> behaviour is unaffected
## Effort Estimate
- Size: XS/S
```
Save to `.claude/epics/<same_epic_as_original>/bug-<original_N>-<slug>.md`
**Step 3 — Create a linked GitHub issue:**
```bash
gh issue create \
--repo "$REPO" \
--title "Bug: <short description>" \
--body "$(cat /tmp/bug-body.md)" \
--label "bug,epic:<epic_name>" \
--json number -q .number
```
The issue body should open with `Fixes / follow-up to #<original_N>` so GitHub auto-links them.
**Step 4 — Update the local file** with the GitHub issue number and rename to `<new_N>.md`.
**Output:**
```
✅ Bug issue created: #<new_N> — "Bug: <short description>"
Linked to: #<original_N>
Epic: <epic_name>
Start fixing it: "start working on issue <new_N>"
```
+165
View File
@@ -0,0 +1,165 @@
# Track — Know Where Things Stand
Tracking operations use bash scripts directly for speed and consistency. The LLM is not needed for these — just run the script and present the output.
---
## Script-First Rule
All tracking operations have a corresponding bash script. Run the script; do not reconstruct the output manually.
Scripts live in `references/scripts/` relative to this skill, but need to run from the **project root** (where `.claude/` lives). Run them as:
```bash
bash <skill_path>/references/scripts/<script>.sh [args]
```
Or if ccpm is installed project-locally:
```bash
bash ccpm/scripts/pm/<script>.sh [args]
```
---
## Project Status
**Trigger**: "what's our status", "project status", "overview"
```bash
bash references/scripts/status.sh
```
Shows: active epics, open issues count, recent activity.
---
## Standup Report
**Trigger**: "standup", "daily standup", "what did we do", "morning update"
```bash
bash references/scripts/standup.sh
```
Shows: what was completed yesterday, what's in progress today, any blockers.
---
## List Epics
**Trigger**: "list epics", "show epics", "what epics do we have"
```bash
bash references/scripts/epic-list.sh
```
---
## Show Epic Details
**Trigger**: "show the <name> epic", "epic details for <name>"
```bash
bash references/scripts/epic-show.sh <name>
```
---
## Epic Status
**Trigger**: "status of the <name> epic", "how far along is <name>"
```bash
bash references/scripts/epic-status.sh <name>
```
Shows: task completion breakdown, active agents, blocking issues.
---
## List PRDs
**Trigger**: "list PRDs", "what PRDs do we have", "show backlog"
```bash
bash references/scripts/prd-list.sh
```
---
## PRD Status
**Trigger**: "PRD status", "which PRDs are parsed", "what's in backlog"
```bash
bash references/scripts/prd-status.sh
```
---
## Search
**Trigger**: "search for <query>", "find issues about <topic>", "look for <term>"
```bash
bash references/scripts/search.sh "<query>"
```
Searches local task files, PRDs, and epics for the query term.
---
## What's In Progress
**Trigger**: "what's in progress", "what are we working on", "active work"
```bash
bash references/scripts/in-progress.sh
```
---
## What's Next
**Trigger**: "what should I work on next", "what's next", "next priority"
```bash
bash references/scripts/next.sh
```
Shows highest-priority open tasks with no blocking dependencies.
---
## What's Blocked
**Trigger**: "what's blocked", "any blockers", "what can't we move on"
```bash
bash references/scripts/blocked.sh
```
---
## Validate Project State
**Trigger**: "validate", "check project state", "is everything consistent"
```bash
bash references/scripts/validate.sh
```
Checks: frontmatter consistency, orphaned files, missing GitHub links, dependency integrity.
---
## When Scripts Fail
If a script fails or the output needs interpretation (e.g., an error in the output, or the user asks "what does this mean"), then step in to explain. But always run the script first — don't guess at what status/standup output would look like.
If `.claude/` directory doesn't exist at all, the project hasn't been initialized. Direct the user to run:
```bash
bash references/scripts/init.sh
```
+7
View File
@@ -0,0 +1,7 @@
Copyright 2026 WH-2099
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+80
View File
@@ -0,0 +1,80 @@
---
name: mermaid
description: Generate Mermaid diagrams from user requirements. Supports flowcharts, sequence diagrams, class diagrams, ER diagrams, Gantt charts, and 18 more diagram types.
allowed-tools: Read Write Edit
metadata:
argument-hint: "[diagram description or requirements]"
---
# Mermaid Diagram Generator
Generate high-quality Mermaid diagram code based on user requirements.
## Workflow
1. **Understand Requirements**: Analyze user description to determine the most suitable diagram type
2. **Read Documentation**: Read the corresponding syntax reference for the diagram type
3. **Generate Code**: Generate Mermaid code following the specification
4. **Apply Styling**: Apply appropriate themes and style configurations
## Diagram Type Reference
Select the appropriate diagram type and read the corresponding documentation:
| Type | Documentation | Use Cases |
| ---- | ------------- | --------- |
| Flowchart | [flowchart.md](references/flowchart.md) | Processes, decisions, steps |
| Sequence Diagram | [sequenceDiagram.md](references/sequenceDiagram.md) | Interactions, messaging, API calls |
| Class Diagram | [classDiagram.md](references/classDiagram.md) | Class structure, inheritance, associations |
| State Diagram | [stateDiagram.md](references/stateDiagram.md) | State machines, state transitions |
| ER Diagram | [entityRelationshipDiagram.md](references/entityRelationshipDiagram.md) | Database design, entity relationships |
| Gantt Chart | [gantt.md](references/gantt.md) | Project planning, timelines |
| Pie Chart | [pie.md](references/pie.md) | Proportions, distributions |
| Mindmap | [mindmap.md](references/mindmap.md) | Hierarchical structures, knowledge graphs |
| Timeline | [timeline.md](references/timeline.md) | Historical events, milestones |
| Git Graph | [gitgraph.md](references/gitgraph.md) | Branches, merges, versions |
| Quadrant Chart | [quadrantChart.md](references/quadrantChart.md) | Four-quadrant analysis |
| Requirement Diagram | [requirementDiagram.md](references/requirementDiagram.md) | Requirements traceability |
| C4 Diagram | [c4.md](references/c4.md) | System architecture (C4 model) |
| Sankey Diagram | [sankey.md](references/sankey.md) | Flow, conversions |
| XY Chart | [xyChart.md](references/xyChart.md) | Line charts, bar charts |
| Block Diagram | [block.md](references/block.md) | System components, modules |
| Packet Diagram | [packet.md](references/packet.md) | Network protocols, data structures |
| Kanban | [kanban.md](references/kanban.md) | Task management, workflows |
| Architecture Diagram | [architecture.md](references/architecture.md) | System architecture |
| Radar Chart | [radar.md](references/radar.md) | Multi-dimensional comparison |
| Treemap | [treemap.md](references/treemap.md) | Hierarchical data visualization |
| User Journey | [userJourney.md](references/userJourney.md) | User experience flows |
| ZenUML | [zenuml.md](references/zenuml.md) | Sequence diagrams (code style) |
## Configuration & Themes
- [Theming](references/config-theming.md) - Custom colors and styles
- [Directives](references/config-directives.md) - Diagram-level configuration
- [Layouts](references/config-layouts.md) - Layout direction and spacing
- [Configuration](references/config-configuration.md) - Global settings
- [Math](references/config-math.md) - LaTeX math support
## Output Specification
Generated Mermaid code should:
1. Be wrapped in ```mermaid code blocks
2. Have correct syntax that renders directly
3. Have clear structure with proper line breaks and indentation
4. Use semantic node naming
5. Include styling when needed to improve visual appearance
## Example Output
```mermaid
flowchart TD
A[Start] --> B{Condition}
B -->|Yes| C[Execute]
B -->|No| D[End]
C --> D
```
---
User requirements: $ARGUMENTS
@@ -0,0 +1,227 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/architecture.md](../../packages/mermaid/src/docs/syntax/architecture.md).
# Architecture Diagrams Documentation (v11.1.0+)
> In the context of mermaid-js, the architecture diagram is used to show the relationship between services and resources commonly found within the Cloud or CI/CD deployments. In an architecture diagram, services (nodes) are connected by edges. Related services can be placed within groups to better illustrate how they are organized.
## Example
```mermaid-example
architecture-beta
group api(cloud)[API]
service db(database)[Database] in api
service disk1(disk)[Storage] in api
service disk2(disk)[Storage] in api
service server(server)[Server] in api
db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
```
```mermaid
architecture-beta
group api(cloud)[API]
service db(database)[Database] in api
service disk1(disk)[Storage] in api
service disk2(disk)[Storage] in api
service server(server)[Server] in api
db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
```
## Syntax
The building blocks of an architecture are `groups`, `services`, `edges`, and `junctions`.
For supporting components, icons are declared by surrounding the icon name with `()`, while labels are declared by surrounding the text with `[]`.
To begin an architecture diagram, use the keyword `architecture-beta`, followed by your groups, services, edges, and junctions. While each of the 3 building blocks can be declared in any order, care must be taken to ensure the identifier was previously declared by another component.
### Groups
The syntax for declaring a group is:
```
group {group id}({icon name})[{title}] (in {parent id})?
```
Put together:
```
group public_api(cloud)[Public API]
```
creates a group identified as `public_api`, uses the icon `cloud`, and has the label `Public API`.
Additionally, groups can be placed within a group using the optional `in` keyword
```
group private_api(cloud)[Private API] in public_api
```
### Services
The syntax for declaring a service is:
```
service {service id}({icon name})[{title}] (in {parent id})?
```
Put together:
```
service database1(database)[My Database]
```
creates the service identified as `database1`, using the icon `database`, with the label `My Database`.
If the service belongs to a group, it can be placed inside it through the optional `in` keyword
```
service database1(database)[My Database] in private_api
```
### Edges
The syntax for declaring an edge is:
```
{serviceId}{{group}}?:{T|B|L|R} {<}?--{>}? {T|B|L|R}:{serviceId}{{group}}?
```
#### Edge Direction
The side of the service the edge comes out of is specified by adding a colon (`:`) to the side of the service connecting to the arrow and adding `L|R|T|B`
For example:
```
db:R -- L:server
```
creates an edge between the services `db` and `server`, with the edge coming out of the right of `db` and the left of `server`.
```
db:T -- L:server
```
creates a 90 degree edge between the services `db` and `server`, with the edge coming out of the top of `db` and the left of `server`.
#### Arrows
Arrows can be added to each side of an edge by adding `<` before the direction on the left, and/or `>` after the direction on the right.
For example:
```
subnet:R --> L:gateway
```
creates an edge with the arrow going into the `gateway` service
#### Edges out of Groups
To have an edge go from a group to another group or service within another group, the `{group}` modifier can be added after the `serviceId`.
For example:
```
service server[Server] in groupOne
service subnet[Subnet] in groupTwo
server{group}:B --> T:subnet{group}
```
creates an edge going out of `groupOne`, adjacent to `server`, and into `groupTwo`, adjacent to `subnet`.
It's important to note that `groupId`s cannot be used for specifying edges and the `{group}` modifier can only be used for services within a group.
### Junctions
Junctions are a special type of node which acts as a potential 4-way split between edges.
The syntax for declaring a junction is:
```
junction {junction id} (in {parent id})?
```
```mermaid-example
architecture-beta
service left_disk(disk)[Disk]
service top_disk(disk)[Disk]
service bottom_disk(disk)[Disk]
service top_gateway(internet)[Gateway]
service bottom_gateway(internet)[Gateway]
junction junctionCenter
junction junctionRight
left_disk:R -- L:junctionCenter
top_disk:B -- T:junctionCenter
bottom_disk:T -- B:junctionCenter
junctionCenter:R -- L:junctionRight
top_gateway:B -- T:junctionRight
bottom_gateway:T -- B:junctionRight
```
```mermaid
architecture-beta
service left_disk(disk)[Disk]
service top_disk(disk)[Disk]
service bottom_disk(disk)[Disk]
service top_gateway(internet)[Gateway]
service bottom_gateway(internet)[Gateway]
junction junctionCenter
junction junctionRight
left_disk:R -- L:junctionCenter
top_disk:B -- T:junctionCenter
bottom_disk:T -- B:junctionCenter
junctionCenter:R -- L:junctionRight
top_gateway:B -- T:junctionRight
bottom_gateway:T -- B:junctionRight
```
## Icons
By default, architecture diagram supports the following icons: `cloud`, `database`, `disk`, `internet`, `server`.
Users can use any of the 200,000+ icons available in iconify.design, or add other custom icons, by [registering an icon pack](../config/icons.md).
After the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack.
```mermaid-example
architecture-beta
group api(logos:aws-lambda)[API]
service db(logos:aws-aurora)[Database] in api
service disk1(logos:aws-glacier)[Storage] in api
service disk2(logos:aws-s3)[Storage] in api
service server(logos:aws-ec2)[Server] in api
db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
```
```mermaid
architecture-beta
group api(logos:aws-lambda)[API]
service db(logos:aws-aurora)[Database] in api
service disk1(logos:aws-glacier)[Storage] in api
service disk2(logos:aws-s3)[Storage] in api
service server(logos:aws-ec2)[Server] in api
db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
```
+753
View File
@@ -0,0 +1,753 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/block.md](../../packages/mermaid/src/docs/syntax/block.md).
# Block Diagrams Documentation
## Introduction to Block Diagrams
```mermaid-example
block
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#969,stroke:#333,stroke-width:4px
```
```mermaid
block
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#969,stroke:#333,stroke-width:4px
```
### Definition and Purpose
Block diagrams are an intuitive and efficient way to represent complex systems, processes, or architectures visually. They are composed of blocks and connectors, where blocks represent the fundamental components or functions, and connectors show the relationship or flow between these components. This method of diagramming is essential in various fields such as engineering, software development, and process management.
The primary purpose of block diagrams is to provide a high-level view of a system, allowing for easy understanding and analysis without delving into the intricate details of each component. This makes them particularly useful for simplifying complex systems and for explaining the overall structure and interaction of components within a system.
Many people use mermaid flowcharts for this purpose. A side-effect of this is that the automatic layout sometimes move shapes to positions that the diagram maker does not want. Block diagrams use a different approach. In this diagram we give the author full control over where the shapes are positioned.
### General Use Cases
Block diagrams have a wide range of applications across various industries and disciplines. Some of the key use cases include:
- **Software Architecture**: In software development, block diagrams can be used to illustrate the architecture of a software application. This includes showing how different modules or services interact, data flow, and high-level component interaction.
- **Network Diagrams**: Block diagrams are ideal for representing network architectures in IT and telecommunications. They can depict how different network devices and services are interconnected, including routers, switches, firewalls, and the flow of data across the network.
- **Process Flowcharts**: In business and manufacturing, block diagrams can be employed to create process flowcharts. These flowcharts represent various stages of a business or manufacturing process, helping to visualize the sequence of steps, decision points, and the flow of control.
- **Electrical Systems**: Engineers use block diagrams to represent electrical systems and circuitry. They can illustrate the high-level structure of an electrical system, the interaction between different electrical components, and the flow of electrical currents.
- **Educational Purposes**: Block diagrams are also extensively used in educational materials to explain complex concepts and systems in a simplified manner. They help in breaking down and visualizing scientific theories, engineering principles, and technological systems.
These examples demonstrate the versatility of block diagrams in providing clear and concise representations of complex systems. Their simplicity and clarity make them a valuable tool for professionals across various fields to communicate complex ideas effectively.
In the following sections, we will delve into the specifics of creating and manipulating block diagrams using Mermaid, covering everything from basic syntax to advanced configurations and styling.
Creating block diagrams with Mermaid is straightforward and accessible. This section introduces the basic syntax and structure needed to start building simple diagrams. Understanding these foundational concepts is key to efficiently utilizing Mermaid for more complex diagramming tasks.
### Simple Block Diagrams
#### Basic Structure
At its core, a block diagram consists of blocks representing different entities or components. In Mermaid, these blocks are easily created using simple text labels. The most basic form of a block diagram can be a series of blocks without any connectors.
**Example - Simple Block Diagram**:
To create a simple block diagram with three blocks labeled 'a', 'b', and 'c', the syntax is as follows:
```mermaid-example
block
a b c
```
```mermaid
block
a b c
```
This example will produce a horizontal sequence of three blocks. Each block is automatically spaced and aligned for optimal readability.
### Defining the number of columns to use
#### Column Usage
While simple block diagrams are linear and straightforward, more complex systems may require a structured layout. Mermaid allows for the organization of blocks into multiple columns, facilitating the creation of more intricate and detailed diagrams.
**Example - Multi-Column Diagram:**
In scenarios where you need to distribute blocks across multiple columns, you can specify the number of columns and arrange the blocks accordingly. Here's how to create a block diagram with three columns and four blocks, where the fourth block appears in a second row:
```mermaid-example
block
columns 3
a b c d
```
```mermaid
block
columns 3
a b c d
```
This syntax instructs Mermaid to arrange the blocks 'a', 'b', 'c', and 'd' across three columns, wrapping to the next row as needed. This feature is particularly useful for representing layered or multi-tiered systems, such as network layers or hierarchical structures.
These basic building blocks of Mermaid's block diagrams provide a foundation for more complex diagramming. The simplicity of the syntax allows for quick creation and iteration of diagrams, making it an efficient tool for visualizing ideas and concepts. In the next section, we'll explore advanced block configuration options, including setting block widths and creating composite blocks.
## 3. Advanced Block Configuration
Building upon the basics, this section delves into more advanced features of block diagramming in Mermaid. These features allow for greater flexibility and complexity in diagram design, accommodating a wider range of use cases and scenarios.
### Setting Block Width
#### Spanning Multiple Columns
In more complex diagrams, you may need blocks that span multiple columns to emphasize certain components or to represent larger entities. Mermaid allows for the adjustment of block widths to cover multiple columns, enhancing the diagram's readability and structure.
**Example - Block Spanning Multiple Columns**:
To create a block diagram where one block spans across two columns, you can specify the desired width for each block:
```mermaid-example
block
columns 3
a["A label"] b:2 c:2 d
```
```mermaid
block
columns 3
a["A label"] b:2 c:2 d
```
In this example, the block labeled "A labels" spans one column, while blocks 'b', 'c' span 2 columns, and 'd' is again allocated its own column. This flexibility in block sizing is crucial for accurately representing systems with components of varying significance or size.
### Creating Composite Blocks
#### Nested Blocks
Composite blocks, or blocks within blocks, are an advanced feature in Mermaid's block diagram syntax. They allow for the representation of nested or hierarchical systems, where one component encompasses several subcomponents.
**Example - Composite Blocks:**
Creating a composite block involves defining a parent block and then nesting other blocks within it. Here's how to define a composite block with nested elements:
```mermaid-example
block
block
D
end
A["A: I am a wide one"]
```
```mermaid
block
block
D
end
A["A: I am a wide one"]
```
In this syntax, 'D' is a nested block within a larger parent block. This feature is particularly useful for depicting complex structures, such as a server with multiple services or a department within a larger organizational framework.
### Column Width Dynamics
#### Adjusting Widths
Mermaid also allows for dynamic adjustment of column widths based on the content of the blocks. The width of the columns is determined by the widest block in the column, ensuring that the diagram remains balanced and readable.
**Example - Dynamic Column Widths:**
In diagrams with varying block sizes, Mermaid automatically adjusts the column widths to fit the largest block in each column. Here's an example:
```mermaid-example
block
columns 3
a:3
block:group1:2
columns 2
h i j k
end
g
block:group2:3
%% columns auto (default)
l m n o p q r
end
```
```mermaid
block
columns 3
a:3
block:group1:2
columns 2
h i j k
end
g
block:group2:3
%% columns auto (default)
l m n o p q r
end
```
This example demonstrates how Mermaid dynamically adjusts the width of the columns to accommodate the widest block, in this case, 'a' and the composite block 'e'. This dynamic adjustment is essential for creating visually balanced and easy-to-understand diagrams.
**Merging Blocks Horizontally:**
In scenarios where you need to stack blocks horizontally, you can use column width to accomplish the task. Blocks can be arranged vertically by putting them in a single column. Here is how you can create a block diagram in which 4 blocks are stacked on top of each other:
```mermaid-example
block
block
columns 1
a["A label"] b c d
end
```
```mermaid
block
block
columns 1
a["A label"] b c d
end
```
In this example, the width of the merged block dynamically adjusts to the width of the largest child block.
With these advanced configuration options, Mermaid's block diagrams can be tailored to represent a wide array of complex systems and structures. The flexibility offered by these features enables users to create diagrams that are both informative and visually appealing. In the following sections, we will explore further capabilities, including different block shapes and linking options.
## 4. Block Varieties and Shapes
Mermaid's block diagrams are not limited to standard rectangular shapes. A variety of block shapes are available, allowing for a more nuanced and tailored representation of different types of information or entities. This section outlines the different block shapes you can use in Mermaid and their specific applications.
### Standard and Special Block Shapes
Mermaid supports a range of block shapes to suit different diagramming needs, from basic geometric shapes to more specialized forms.
#### Example - Round Edged Block
To create a block with round edges, which can be used to represent a softer or more flexible component:
```mermaid-example
block
id1("This is the text in the box")
```
```mermaid
block
id1("This is the text in the box")
```
#### Example - Stadium-Shaped Block
A stadium-shaped block, resembling an elongated circle, can be used for components that are process-oriented:
```mermaid-example
block
id1(["This is the text in the box"])
```
```mermaid
block
id1(["This is the text in the box"])
```
#### Example - Subroutine Shape
For representing subroutines or contained processes, a block with double vertical lines is useful:
```mermaid-example
block
id1[["This is the text in the box"]]
```
```mermaid
block
id1[["This is the text in the box"]]
```
#### Example - Cylindrical Shape
The cylindrical shape is ideal for representing databases or storage components:
```mermaid-example
block
id1[("Database")]
```
```mermaid
block
id1[("Database")]
```
#### Example - Circle Shape
A circle can be used for centralized or pivotal components:
```mermaid-example
block
id1(("This is the text in the circle"))
```
```mermaid
block
id1(("This is the text in the circle"))
```
#### Example - Asymmetric, Rhombus, and Hexagon Shapes
For decision points, use a rhombus, and for unique or specialized processes, asymmetric and hexagon shapes can be utilized:
**Asymmetric**
```mermaid-example
block
id1>"This is the text in the box"]
```
```mermaid
block
id1>"This is the text in the box"]
```
**Rhombus**
```mermaid-example
block
id1{"This is the text in the box"}
```
```mermaid
block
id1{"This is the text in the box"}
```
**Hexagon**
```mermaid-example
block
id1{{"This is the text in the box"}}
```
```mermaid
block
id1{{"This is the text in the box"}}
```
#### Example - Parallelogram and Trapezoid Shapes
Parallelogram and trapezoid shapes are perfect for inputs/outputs and transitional processes:
```mermaid-example
block
id1[/"This is the text in the box"/]
id2[\"This is the text in the box"\]
A[/"Christmas"\]
B[\"Go shopping"/]
```
```mermaid
block
id1[/"This is the text in the box"/]
id2[\"This is the text in the box"\]
A[/"Christmas"\]
B[\"Go shopping"/]
```
#### Example - Double Circle
For highlighting critical or high-priority components, a double circle can be effective:
```mermaid-example
block
id1((("This is the text in the circle")))
```
```mermaid
block
id1((("This is the text in the circle")))
```
### Block Arrows and Space Blocks
Mermaid also offers unique shapes like block arrows and space blocks for directional flow and spacing.
#### Example - Block Arrows
Block arrows can visually indicate direction or flow within a process:
```mermaid-example
block
blockArrowId<["Label"]>(right)
blockArrowId2<["Label"]>(left)
blockArrowId3<["Label"]>(up)
blockArrowId4<["Label"]>(down)
blockArrowId5<["Label"]>(x)
blockArrowId6<["Label"]>(y)
blockArrowId7<["Label"]>(x, down)
```
```mermaid
block
blockArrowId<["Label"]>(right)
blockArrowId2<["Label"]>(left)
blockArrowId3<["Label"]>(up)
blockArrowId4<["Label"]>(down)
blockArrowId5<["Label"]>(x)
blockArrowId6<["Label"]>(y)
blockArrowId7<["Label"]>(x, down)
```
#### Example - Space Blocks
Space blocks can be used to create intentional empty spaces in the diagram, which is useful for layout and readability:
```mermaid-example
block
columns 3
a space b
c d e
```
```mermaid
block
columns 3
a space b
c d e
```
or
```mermaid-example
block
ida space:3 idb idc
```
```mermaid
block
ida space:3 idb idc
```
Note that you can set how many columns the space block occupied using the number notation `space:num` where num is a number indicating the num columns width. You can also use `space` which defaults to one column.
The variety of shapes and special blocks in Mermaid enhances the expressive power of block diagrams, allowing for more accurate and context-specific representations. These options give users the flexibility to create diagrams that are both informative and visually appealing. In the next sections, we will explore the ways to connect these blocks and customize their appearance.
### Standard and Special Block Shapes
Discuss the various shapes available for blocks, including standard shapes and special forms like block arrows and space blocks.
## 5. Connecting Blocks with Edges
One of the key features of block diagrams in Mermaid is the ability to connect blocks using various types of edges or links. This section explores the different ways blocks can be interconnected to represent relationships and flows between components.
### Basic Linking and Arrow Types
The most fundamental aspect of connecting blocks is the use of arrows or links. These connectors depict the relationships or the flow of information between the blocks. Mermaid offers a range of arrow types to suit different diagramming needs.
**Example - Basic Links**
A simple link with an arrow can be created to show direction or flow from one block to another:
```mermaid-example
block
A space B
A-->B
```
```mermaid
block
A space B
A-->B
```
This example illustrates a direct connection from block 'A' to block 'B', using a straightforward arrow.
This syntax creates a line connecting 'A' and 'B', implying a relationship or connection without indicating a specific direction.
### Text on Links
In addition to connecting blocks, it's often necessary to describe or label the relationship. Mermaid allows for the inclusion of text on links, providing context to the connections.
Example - Text with Links
To add text to a link, the syntax includes the text within the link definition:
```mermaid-example
block
A space:2 B
A-- "X" -->B
```
```mermaid
block
A space:2 B
A-- "X" -->B
```
This example show how to add descriptive text to the links, enhancing the information conveyed by the diagram.
Example - Edges and Styles:
```mermaid-example
block
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#939,stroke:#333,stroke-width:4px
```
```mermaid
block
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#939,stroke:#333,stroke-width:4px
```
## 6. Styling and Customization
Beyond the structure and layout of block diagrams, Mermaid offers extensive styling options. These customization features allow for the creation of more visually distinctive and informative diagrams. This section covers how to apply individual styles to blocks and how to use classes for consistent styling across multiple elements.
### Individual Block Styling
Mermaid enables detailed styling of individual blocks, allowing you to apply various CSS properties such as color, stroke, and border thickness. This feature is especially useful for highlighting specific parts of a diagram or for adhering to certain visual themes.
#### Example - Styling a Single Block
To apply custom styles to a block, you can use the `style` keyword followed by the block identifier and the desired CSS properties:
```mermaid-example
block
id1 space id2
id1("Start")-->id2("Stop")
style id1 fill:#636,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
```
```mermaid
block
id1 space id2
id1("Start")-->id2("Stop")
style id1 fill:#636,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
```
### Class Styling
Mermaid enables applying styling to classes, which could make styling easier if you want to apply a certain set of styles to multiple elements, as you could just link those elements to a class.
#### Example - Styling a Single Class
```mermaid-example
block
A space B
A-->B
classDef blue fill:#6e6ce6,stroke:#333,stroke-width:4px;
class A blue
style B fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
```
```mermaid
block
A space B
A-->B
classDef blue fill:#6e6ce6,stroke:#333,stroke-width:4px;
class A blue
style B fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
```
In this example, a class named 'blue' is defined and applied to block 'A', while block 'B' receives individual styling. This demonstrates the flexibility of Mermaid in applying both shared and unique styles within the same diagram.
The ability to style blocks individually or through classes provides a powerful tool for enhancing the visual impact and clarity of block diagrams. Whether emphasizing certain elements or maintaining a cohesive design across the diagram, these styling capabilities are central to effective diagramming. The next sections will present practical examples and use cases, followed by tips for troubleshooting common issues.
### 7. Practical Examples and Use Cases
The versatility of Mermaid's block diagrams becomes evident when applied to real-world scenarios. This section provides practical examples demonstrating the application of various features discussed in previous sections. These examples showcase how block diagrams can be used to represent complex systems and processes in an accessible and informative manner.
### Detailed Examples Illustrating Various Features
Combining the elements of structure, linking, and styling, we can create comprehensive diagrams that serve specific purposes in different contexts.
#### Example - System Architecture
Illustrating a simple software system architecture with interconnected components:
```mermaid-example
block
columns 3
Frontend blockArrowId6<[" "]>(right) Backend
space:2 down<[" "]>(down)
Disk left<[" "]>(left) Database[("Database")]
classDef front fill:#696,stroke:#333;
classDef back fill:#969,stroke:#333;
class Frontend front
class Backend,Database back
```
```mermaid
block
columns 3
Frontend blockArrowId6<[" "]>(right) Backend
space:2 down<[" "]>(down)
Disk left<[" "]>(left) Database[("Database")]
classDef front fill:#696,stroke:#333;
classDef back fill:#969,stroke:#333;
class Frontend front
class Backend,Database back
```
This example shows a basic architecture with a frontend, backend, and database. The blocks are styled to differentiate between types of components.
#### Example - Business Process Flow
Representing a business process flow with decision points and multiple stages:
```mermaid-example
block
columns 3
Start(("Start")) space:2
down<[" "]>(down) space:2
Decision{{"Make Decision"}} right<["Yes"]>(right) Process1["Process A"]
downAgain<["No"]>(down) space r3<["Done"]>(down)
Process2["Process B"] r2<["Done"]>(right) End(("End"))
style Start fill:#969;
style End fill:#696;
```
```mermaid
block
columns 3
Start(("Start")) space:2
down<[" "]>(down) space:2
Decision{{"Make Decision"}} right<["Yes"]>(right) Process1["Process A"]
downAgain<["No"]>(down) space r3<["Done"]>(down)
Process2["Process B"] r2<["Done"]>(right) End(("End"))
style Start fill:#969;
style End fill:#696;
```
These practical examples and scenarios underscore the utility of Mermaid block diagrams in simplifying and effectively communicating complex information across various domains.
The next section, 'Troubleshooting and Common Issues', will provide insights into resolving common challenges encountered when working with Mermaid block diagrams, ensuring a smooth diagramming experience.
## 8. Troubleshooting and Common Issues
Working with Mermaid block diagrams can sometimes present challenges, especially as the complexity of the diagrams increases. This section aims to provide guidance on resolving common issues and offers tips for managing more intricate diagram structures.
### Common Syntax Errors
Understanding and avoiding common syntax errors is key to a smooth experience with Mermaid diagrams.
#### Example - Incorrect Linking
A common mistake is incorrect linking syntax, which can lead to unexpected results or broken diagrams:
```
block
A - B
```
**Correction**:
Ensure that links between blocks are correctly specified with arrows (--> or ---) to define the direction and type of connection. Also remember that one of the fundamentals for block diagram is to give the author full control of where the boxes are positioned so in the example you need to add a space between the boxes:
```mermaid-example
block
A space B
A --> B
```
```mermaid
block
A space B
A --> B
```
#### Example - Misplaced Styling
Applying styles in the wrong context or with incorrect syntax can lead to blocks not being styled as intended:
```mermaid-example
block
A
style A fill#969;
```
```mermaid
block
A
style A fill#969;
```
**Correction:**
Correct the syntax by ensuring proper separation of style properties with commas and using the correct CSS property format:
```mermaid-example
block
A
style A fill:#969,stroke:#333;
```
```mermaid
block
A
style A fill:#969,stroke:#333;
```
### Tips for Complex Diagram Structures
Managing complexity in Mermaid diagrams involves planning and employing best practices.
#### Modular Design
Break down complex diagrams into smaller, more manageable components. This approach not only makes the diagram easier to understand but also simplifies the creation and maintenance process.
#### Consistent Styling
Use classes to maintain consistent styling across similar elements. This not only saves time but also ensures a cohesive and professional appearance.
#### Comments and Documentation
Use comments with `%%` within the Mermaid syntax to document the purpose of various parts of the diagram. This practice is invaluable for maintaining clarity, especially when working in teams or returning to a diagram after some time.
With these troubleshooting tips and best practices, you can effectively manage and resolve common issues in Mermaid block diagrams. The final section, 'Conclusion', will summarize the key points covered in this documentation and invite user feedback for continuous improvement.
+619
View File
@@ -0,0 +1,619 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/c4.md](../../packages/mermaid/src/docs/syntax/c4.md).
# C4 Diagrams
> C4 Diagram: This is an experimental diagram for now. The syntax and properties can change in future releases. Proper documentation will be provided when the syntax is stable.
Mermaid's C4 diagram syntax is compatible with plantUML. See example below:
```mermaid-example
C4Context
title System Context diagram for Internet Banking System
Enterprise_Boundary(b0, "BankBoundary0") {
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
Person(customerB, "Banking Customer B")
Person_Ext(customerC, "Banking Customer C", "desc")
Person(customerD, "Banking Customer D", "A customer of the bank, <br/> with personal bank accounts.")
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
Enterprise_Boundary(b1, "BankBoundary") {
SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
System_Boundary(b2, "BankBoundary2") {
System(SystemA, "Banking System A")
System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts. next line.")
}
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.")
Boundary(b3, "BankBoundary3", "boundary") {
SystemQueue(SystemF, "Banking System F Queue", "A system of the bank.")
SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.")
}
}
}
BiRel(customerA, SystemAA, "Uses")
BiRel(SystemAA, SystemE, "Uses")
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
Rel(SystemC, customerA, "Sends e-mails to")
UpdateElementStyle(customerA, $fontColor="red", $bgColor="grey", $borderColor="red")
UpdateRelStyle(customerA, SystemAA, $textColor="blue", $lineColor="blue", $offsetX="5")
UpdateRelStyle(SystemAA, SystemE, $textColor="blue", $lineColor="blue", $offsetY="-10")
UpdateRelStyle(SystemAA, SystemC, $textColor="blue", $lineColor="blue", $offsetY="-40", $offsetX="-50")
UpdateRelStyle(SystemC, customerA, $textColor="red", $lineColor="red", $offsetX="-50", $offsetY="20")
UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")
```
```mermaid
C4Context
title System Context diagram for Internet Banking System
Enterprise_Boundary(b0, "BankBoundary0") {
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
Person(customerB, "Banking Customer B")
Person_Ext(customerC, "Banking Customer C", "desc")
Person(customerD, "Banking Customer D", "A customer of the bank, <br/> with personal bank accounts.")
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
Enterprise_Boundary(b1, "BankBoundary") {
SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
System_Boundary(b2, "BankBoundary2") {
System(SystemA, "Banking System A")
System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts. next line.")
}
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.")
Boundary(b3, "BankBoundary3", "boundary") {
SystemQueue(SystemF, "Banking System F Queue", "A system of the bank.")
SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.")
}
}
}
BiRel(customerA, SystemAA, "Uses")
BiRel(SystemAA, SystemE, "Uses")
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
Rel(SystemC, customerA, "Sends e-mails to")
UpdateElementStyle(customerA, $fontColor="red", $bgColor="grey", $borderColor="red")
UpdateRelStyle(customerA, SystemAA, $textColor="blue", $lineColor="blue", $offsetX="5")
UpdateRelStyle(SystemAA, SystemE, $textColor="blue", $lineColor="blue", $offsetY="-10")
UpdateRelStyle(SystemAA, SystemC, $textColor="blue", $lineColor="blue", $offsetY="-40", $offsetX="-50")
UpdateRelStyle(SystemC, customerA, $textColor="red", $lineColor="red", $offsetX="-50", $offsetY="20")
UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")
```
For an example, see the source code demos/index.html
5 types of C4 charts are supported.
- System Context (C4Context)
- Container diagram (C4Container)
- Component diagram (C4Component)
- Dynamic diagram (C4Dynamic)
- Deployment diagram (C4Deployment)
Please refer to the linked document [C4-PlantUML syntax](https://github.com/plantuml-stdlib/C4-PlantUML/blob/master/README.md) for how to write the C4 diagram.
C4 diagram is fixed style, such as css color, so different css is not provided under different skins.
updateElementStyle and UpdateElementStyle are written in the diagram last part. updateElementStyle is inconsistent with the original definition and updates the style of the relationship, including the offset of the text label relative to the original position.
The layout does not use a fully automated layout algorithm. The position of shapes is adjusted by changing the order in which statements are written. So there is no plan to support the following Layout statements.
The number of shapes per row and the number of boundaries can be adjusted using UpdateLayoutConfig.
- Layout
- Lay_U, Lay_Up
- Lay_D, Lay_Down
- Lay_L, Lay_Left
- Lay_R, Lay_Right
The following unfinished features are not supported in the short term.
- [ ] sprite
- [ ] tags
- [ ] link
- [ ] Legend
- [x] System Context
- [x] Person(alias, label, ?descr, ?sprite, ?tags, $link)
- [x] Person_Ext
- [x] System(alias, label, ?descr, ?sprite, ?tags, $link)
- [x] SystemDb
- [x] SystemQueue
- [x] System_Ext
- [x] SystemDb_Ext
- [x] SystemQueue_Ext
- [x] Boundary(alias, label, ?type, ?tags, $link)
- [x] Enterprise_Boundary(alias, label, ?tags, $link)
- [x] System_Boundary
- [x] Container diagram
- [x] Container(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
- [x] ContainerDb
- [x] ContainerQueue
- [x] Container_Ext
- [x] ContainerDb_Ext
- [x] ContainerQueue_Ext
- [x] Container_Boundary(alias, label, ?tags, $link)
- [x] Component diagram
- [x] Component(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
- [x] ComponentDb
- [x] ComponentQueue
- [x] Component_Ext
- [x] ComponentDb_Ext
- [x] ComponentQueue_Ext
- [x] Dynamic diagram
- [x] RelIndex(index, from, to, label, ?tags, $link)
- [x] Deployment diagram
- [x] Deployment_Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link)
- [x] Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link): short name of Deployment_Node()
- [x] Node_L(alias, label, ?type, ?descr, ?sprite, ?tags, $link): left aligned Node()
- [x] Node_R(alias, label, ?type, ?descr, ?sprite, ?tags, $link): right aligned Node()
- [x] Relationship Types
- [x] Rel(from, to, label, ?techn, ?descr, ?sprite, ?tags, $link)
- [x] BiRel (bidirectional relationship)
- [x] Rel_U, Rel_Up
- [x] Rel_D, Rel_Down
- [x] Rel_L, Rel_Left
- [x] Rel_R, Rel_Right
- [x] Rel_Back
- [x] RelIndex \* Compatible with C4-PlantUML syntax, but ignores the index parameter. The sequence number is determined by the order in which the rel statements are written.
- [ ] Custom tags/stereotypes support and skin param updates
- [ ] AddElementTag(tagStereo, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape, ?sprite, ?techn, ?legendText, ?legendSprite): Introduces a new element tag. The styles of the tagged elements are updated and the tag is displayed in the calculated legend.
- [ ] AddRelTag(tagStereo, ?textColor, ?lineColor, ?lineStyle, ?sprite, ?techn, ?legendText, ?legendSprite): Introduces a new Relationship tag. The styles of the tagged relationships are updated and the tag is displayed in the calculated legend.
- [x] UpdateElementStyle(elementName, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape, ?sprite, ?techn, ?legendText, ?legendSprite): This call updates the default style of the elements (component, ...) and creates no additional legend entry.
- [x] UpdateRelStyle(from, to, ?textColor, ?lineColor, ?offsetX, ?offsetY): This call updates the default relationship colors and creates no additional legend entry. Two new parameters, offsetX and offsetY, are added to set the offset of the original position of the text.
- [ ] RoundedBoxShape(): This call returns the name of the rounded box shape and can be used as ?shape argument.
- [ ] EightSidedShape(): This call returns the name of the eight sided shape and can be used as ?shape argument.
- [ ] DashedLine(): This call returns the name of the dashed line and can be used as ?lineStyle argument.
- [ ] DottedLine(): This call returns the name of the dotted line and can be used as ?lineStyle argument.
- [ ] BoldLine(): This call returns the name of the bold line and can be used as ?lineStyle argument.
- [x] UpdateLayoutConfig(?c4ShapeInRow, ?c4BoundaryInRow): New. This call updates the default c4ShapeInRow(4) and c4BoundaryInRow(2).
There are two ways to assign parameters with question marks. One uses the non-named parameter assignment method in the order of the parameters, and the other uses the named parameter assignment method, where the name must start with a $ symbol.
Example: UpdateRelStyle(from, to, ?textColor, ?lineColor, ?offsetX, ?offsetY)
```
UpdateRelStyle(customerA, bankA, "red", "blue", "-40", "60")
UpdateRelStyle(customerA, bankA, $offsetX="-40", $offsetY="60", $lineColor="blue", $textColor="red")
UpdateRelStyle(customerA, bankA, $offsetY="60")
```
## C4 System Context Diagram (C4Context)
```mermaid-example
C4Context
title System Context diagram for Internet Banking System
Enterprise_Boundary(b0, "BankBoundary0") {
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
Person(customerB, "Banking Customer B")
Person_Ext(customerC, "Banking Customer C", "desc")
Person(customerD, "Banking Customer D", "A customer of the bank, <br/> with personal bank accounts.")
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
Enterprise_Boundary(b1, "BankBoundary") {
SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
System_Boundary(b2, "BankBoundary2") {
System(SystemA, "Banking System A")
System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts. next line.")
}
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.")
Boundary(b3, "BankBoundary3", "boundary") {
SystemQueue(SystemF, "Banking System F Queue", "A system of the bank.")
SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.")
}
}
}
BiRel(customerA, SystemAA, "Uses")
BiRel(SystemAA, SystemE, "Uses")
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
Rel(SystemC, customerA, "Sends e-mails to")
UpdateElementStyle(customerA, $fontColor="red", $bgColor="grey", $borderColor="red")
UpdateRelStyle(customerA, SystemAA, $textColor="blue", $lineColor="blue", $offsetX="5")
UpdateRelStyle(SystemAA, SystemE, $textColor="blue", $lineColor="blue", $offsetY="-10")
UpdateRelStyle(SystemAA, SystemC, $textColor="blue", $lineColor="blue", $offsetY="-40", $offsetX="-50")
UpdateRelStyle(SystemC, customerA, $textColor="red", $lineColor="red", $offsetX="-50", $offsetY="20")
UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")
```
```mermaid
C4Context
title System Context diagram for Internet Banking System
Enterprise_Boundary(b0, "BankBoundary0") {
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
Person(customerB, "Banking Customer B")
Person_Ext(customerC, "Banking Customer C", "desc")
Person(customerD, "Banking Customer D", "A customer of the bank, <br/> with personal bank accounts.")
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
Enterprise_Boundary(b1, "BankBoundary") {
SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
System_Boundary(b2, "BankBoundary2") {
System(SystemA, "Banking System A")
System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts. next line.")
}
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.")
Boundary(b3, "BankBoundary3", "boundary") {
SystemQueue(SystemF, "Banking System F Queue", "A system of the bank.")
SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.")
}
}
}
BiRel(customerA, SystemAA, "Uses")
BiRel(SystemAA, SystemE, "Uses")
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
Rel(SystemC, customerA, "Sends e-mails to")
UpdateElementStyle(customerA, $fontColor="red", $bgColor="grey", $borderColor="red")
UpdateRelStyle(customerA, SystemAA, $textColor="blue", $lineColor="blue", $offsetX="5")
UpdateRelStyle(SystemAA, SystemE, $textColor="blue", $lineColor="blue", $offsetY="-10")
UpdateRelStyle(SystemAA, SystemC, $textColor="blue", $lineColor="blue", $offsetY="-40", $offsetX="-50")
UpdateRelStyle(SystemC, customerA, $textColor="red", $lineColor="red", $offsetX="-50", $offsetY="20")
UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")
```
## C4 Container diagram (C4Container)
```mermaid-example
C4Container
title Container diagram for Internet Banking System
System_Ext(email_system, "E-Mail System", "The internal Microsoft Exchange system", $tags="v1.0")
Person(customer, Customer, "A customer of the bank, with personal bank accounts", $tags="v1.0")
Container_Boundary(c1, "Internet Banking") {
Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to customers via their web browser")
Container_Ext(mobile_app, "Mobile App", "C#, Xamarin", "Provides a limited subset of the Internet banking functionality to customers via their mobile device")
Container(web_app, "Web Application", "Java, Spring MVC", "Delivers the static content and the Internet banking SPA")
ContainerDb(database, "Database", "SQL Database", "Stores user registration information, hashed auth credentials, access logs, etc.")
ContainerDb_Ext(backend_api, "API Application", "Java, Docker Container", "Provides Internet banking functionality via API")
}
System_Ext(banking_system, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
Rel(customer, web_app, "Uses", "HTTPS")
UpdateRelStyle(customer, web_app, $offsetY="60", $offsetX="90")
Rel(customer, spa, "Uses", "HTTPS")
UpdateRelStyle(customer, spa, $offsetY="-40")
Rel(customer, mobile_app, "Uses")
UpdateRelStyle(customer, mobile_app, $offsetY="-30")
Rel(web_app, spa, "Delivers")
UpdateRelStyle(web_app, spa, $offsetX="130")
Rel(spa, backend_api, "Uses", "async, JSON/HTTPS")
Rel(mobile_app, backend_api, "Uses", "async, JSON/HTTPS")
Rel_Back(database, backend_api, "Reads from and writes to", "sync, JDBC")
Rel(email_system, customer, "Sends e-mails to")
UpdateRelStyle(email_system, customer, $offsetX="-45")
Rel(backend_api, email_system, "Sends e-mails using", "sync, SMTP")
UpdateRelStyle(backend_api, email_system, $offsetY="-60")
Rel(backend_api, banking_system, "Uses", "sync/async, XML/HTTPS")
UpdateRelStyle(backend_api, banking_system, $offsetY="-50", $offsetX="-140")
```
```mermaid
C4Container
title Container diagram for Internet Banking System
System_Ext(email_system, "E-Mail System", "The internal Microsoft Exchange system", $tags="v1.0")
Person(customer, Customer, "A customer of the bank, with personal bank accounts", $tags="v1.0")
Container_Boundary(c1, "Internet Banking") {
Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to customers via their web browser")
Container_Ext(mobile_app, "Mobile App", "C#, Xamarin", "Provides a limited subset of the Internet banking functionality to customers via their mobile device")
Container(web_app, "Web Application", "Java, Spring MVC", "Delivers the static content and the Internet banking SPA")
ContainerDb(database, "Database", "SQL Database", "Stores user registration information, hashed auth credentials, access logs, etc.")
ContainerDb_Ext(backend_api, "API Application", "Java, Docker Container", "Provides Internet banking functionality via API")
}
System_Ext(banking_system, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
Rel(customer, web_app, "Uses", "HTTPS")
UpdateRelStyle(customer, web_app, $offsetY="60", $offsetX="90")
Rel(customer, spa, "Uses", "HTTPS")
UpdateRelStyle(customer, spa, $offsetY="-40")
Rel(customer, mobile_app, "Uses")
UpdateRelStyle(customer, mobile_app, $offsetY="-30")
Rel(web_app, spa, "Delivers")
UpdateRelStyle(web_app, spa, $offsetX="130")
Rel(spa, backend_api, "Uses", "async, JSON/HTTPS")
Rel(mobile_app, backend_api, "Uses", "async, JSON/HTTPS")
Rel_Back(database, backend_api, "Reads from and writes to", "sync, JDBC")
Rel(email_system, customer, "Sends e-mails to")
UpdateRelStyle(email_system, customer, $offsetX="-45")
Rel(backend_api, email_system, "Sends e-mails using", "sync, SMTP")
UpdateRelStyle(backend_api, email_system, $offsetY="-60")
Rel(backend_api, banking_system, "Uses", "sync/async, XML/HTTPS")
UpdateRelStyle(backend_api, banking_system, $offsetY="-50", $offsetX="-140")
```
## C4 Component diagram (C4Component)
```mermaid-example
C4Component
title Component diagram for Internet Banking System - API Application
Container(spa, "Single Page Application", "javascript and angular", "Provides all the internet banking functionality to customers via their web browser.")
Container(ma, "Mobile App", "Xamarin", "Provides a limited subset to the internet banking functionality to customers via their mobile device.")
ContainerDb(db, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
System_Ext(mbs, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
Container_Boundary(api, "API Application") {
Component(sign, "Sign In Controller", "MVC Rest Controller", "Allows users to sign in to the internet banking system")
Component(accounts, "Accounts Summary Controller", "MVC Rest Controller", "Provides customers with a summary of their bank accounts")
Component(security, "Security Component", "Spring Bean", "Provides functionality related to singing in, changing passwords, etc.")
Component(mbsfacade, "Mainframe Banking System Facade", "Spring Bean", "A facade onto the mainframe banking system.")
Rel(sign, security, "Uses")
Rel(accounts, mbsfacade, "Uses")
Rel(security, db, "Read & write to", "JDBC")
Rel(mbsfacade, mbs, "Uses", "XML/HTTPS")
}
Rel_Back(spa, sign, "Uses", "JSON/HTTPS")
Rel(spa, accounts, "Uses", "JSON/HTTPS")
Rel(ma, sign, "Uses", "JSON/HTTPS")
Rel(ma, accounts, "Uses", "JSON/HTTPS")
UpdateRelStyle(spa, sign, $offsetY="-40")
UpdateRelStyle(spa, accounts, $offsetX="40", $offsetY="40")
UpdateRelStyle(ma, sign, $offsetX="-90", $offsetY="40")
UpdateRelStyle(ma, accounts, $offsetY="-40")
UpdateRelStyle(sign, security, $offsetX="-160", $offsetY="10")
UpdateRelStyle(accounts, mbsfacade, $offsetX="140", $offsetY="10")
UpdateRelStyle(security, db, $offsetY="-40")
UpdateRelStyle(mbsfacade, mbs, $offsetY="-40")
```
```mermaid
C4Component
title Component diagram for Internet Banking System - API Application
Container(spa, "Single Page Application", "javascript and angular", "Provides all the internet banking functionality to customers via their web browser.")
Container(ma, "Mobile App", "Xamarin", "Provides a limited subset to the internet banking functionality to customers via their mobile device.")
ContainerDb(db, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
System_Ext(mbs, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
Container_Boundary(api, "API Application") {
Component(sign, "Sign In Controller", "MVC Rest Controller", "Allows users to sign in to the internet banking system")
Component(accounts, "Accounts Summary Controller", "MVC Rest Controller", "Provides customers with a summary of their bank accounts")
Component(security, "Security Component", "Spring Bean", "Provides functionality related to singing in, changing passwords, etc.")
Component(mbsfacade, "Mainframe Banking System Facade", "Spring Bean", "A facade onto the mainframe banking system.")
Rel(sign, security, "Uses")
Rel(accounts, mbsfacade, "Uses")
Rel(security, db, "Read & write to", "JDBC")
Rel(mbsfacade, mbs, "Uses", "XML/HTTPS")
}
Rel_Back(spa, sign, "Uses", "JSON/HTTPS")
Rel(spa, accounts, "Uses", "JSON/HTTPS")
Rel(ma, sign, "Uses", "JSON/HTTPS")
Rel(ma, accounts, "Uses", "JSON/HTTPS")
UpdateRelStyle(spa, sign, $offsetY="-40")
UpdateRelStyle(spa, accounts, $offsetX="40", $offsetY="40")
UpdateRelStyle(ma, sign, $offsetX="-90", $offsetY="40")
UpdateRelStyle(ma, accounts, $offsetY="-40")
UpdateRelStyle(sign, security, $offsetX="-160", $offsetY="10")
UpdateRelStyle(accounts, mbsfacade, $offsetX="140", $offsetY="10")
UpdateRelStyle(security, db, $offsetY="-40")
UpdateRelStyle(mbsfacade, mbs, $offsetY="-40")
```
## C4 Dynamic diagram (C4Dynamic)
```mermaid-example
C4Dynamic
title Dynamic diagram for Internet Banking System - API Application
ContainerDb(c4, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
Container(c1, "Single-Page Application", "JavaScript and Angular", "Provides all of the Internet banking functionality to customers via their web browser.")
Container_Boundary(b, "API Application") {
Component(c3, "Security Component", "Spring Bean", "Provides functionality Related to signing in, changing passwords, etc.")
Component(c2, "Sign In Controller", "Spring MVC Rest Controller", "Allows users to sign in to the Internet Banking System.")
}
Rel(c1, c2, "Submits credentials to", "JSON/HTTPS")
Rel(c2, c3, "Calls isAuthenticated() on")
Rel(c3, c4, "select * from users where username = ?", "JDBC")
UpdateRelStyle(c1, c2, $textColor="red", $offsetY="-40")
UpdateRelStyle(c2, c3, $textColor="red", $offsetX="-40", $offsetY="60")
UpdateRelStyle(c3, c4, $textColor="red", $offsetY="-40", $offsetX="10")
```
```mermaid
C4Dynamic
title Dynamic diagram for Internet Banking System - API Application
ContainerDb(c4, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
Container(c1, "Single-Page Application", "JavaScript and Angular", "Provides all of the Internet banking functionality to customers via their web browser.")
Container_Boundary(b, "API Application") {
Component(c3, "Security Component", "Spring Bean", "Provides functionality Related to signing in, changing passwords, etc.")
Component(c2, "Sign In Controller", "Spring MVC Rest Controller", "Allows users to sign in to the Internet Banking System.")
}
Rel(c1, c2, "Submits credentials to", "JSON/HTTPS")
Rel(c2, c3, "Calls isAuthenticated() on")
Rel(c3, c4, "select * from users where username = ?", "JDBC")
UpdateRelStyle(c1, c2, $textColor="red", $offsetY="-40")
UpdateRelStyle(c2, c3, $textColor="red", $offsetX="-40", $offsetY="60")
UpdateRelStyle(c3, c4, $textColor="red", $offsetY="-40", $offsetX="10")
```
## C4 Deployment diagram (C4Deployment)
```mermaid-example
C4Deployment
title Deployment Diagram for Internet Banking System - Live
Deployment_Node(mob, "Customer's mobile device", "Apple IOS or Android"){
Container(mobile, "Mobile App", "Xamarin", "Provides a limited subset of the Internet Banking functionality to customers via their mobile device.")
}
Deployment_Node(comp, "Customer's computer", "Microsoft Windows or Apple macOS"){
Deployment_Node(browser, "Web Browser", "Google Chrome, Mozilla Firefox,<br/> Apple Safari or Microsoft Edge"){
Container(spa, "Single Page Application", "JavaScript and Angular", "Provides all of the Internet Banking functionality to customers via their web browser.")
}
}
Deployment_Node(plc, "Big Bank plc", "Big Bank plc data center"){
Deployment_Node(dn, "bigbank-api*** x8", "Ubuntu 16.04 LTS"){
Deployment_Node(apache, "Apache Tomcat", "Apache Tomcat 8.x"){
Container(api, "API Application", "Java and Spring MVC", "Provides Internet Banking functionality via a JSON/HTTPS API.")
}
}
Deployment_Node(bb2, "bigbank-web*** x4", "Ubuntu 16.04 LTS"){
Deployment_Node(apache2, "Apache Tomcat", "Apache Tomcat 8.x"){
Container(web, "Web Application", "Java and Spring MVC", "Delivers the static content and the Internet Banking single page application.")
}
}
Deployment_Node(bigbankdb01, "bigbank-db01", "Ubuntu 16.04 LTS"){
Deployment_Node(oracle, "Oracle - Primary", "Oracle 12c"){
ContainerDb(db, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
}
}
Deployment_Node(bigbankdb02, "bigbank-db02", "Ubuntu 16.04 LTS") {
Deployment_Node(oracle2, "Oracle - Secondary", "Oracle 12c") {
ContainerDb(db2, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
}
}
}
Rel(mobile, api, "Makes API calls to", "json/HTTPS")
Rel(spa, api, "Makes API calls to", "json/HTTPS")
Rel_U(web, spa, "Delivers to the customer's web browser")
Rel(api, db, "Reads from and writes to", "JDBC")
Rel(api, db2, "Reads from and writes to", "JDBC")
Rel_R(db, db2, "Replicates data to")
UpdateRelStyle(spa, api, $offsetY="-40")
UpdateRelStyle(web, spa, $offsetY="-40")
UpdateRelStyle(api, db, $offsetY="-20", $offsetX="5")
UpdateRelStyle(api, db2, $offsetX="-40", $offsetY="-20")
UpdateRelStyle(db, db2, $offsetY="-10")
```
```mermaid
C4Deployment
title Deployment Diagram for Internet Banking System - Live
Deployment_Node(mob, "Customer's mobile device", "Apple IOS or Android"){
Container(mobile, "Mobile App", "Xamarin", "Provides a limited subset of the Internet Banking functionality to customers via their mobile device.")
}
Deployment_Node(comp, "Customer's computer", "Microsoft Windows or Apple macOS"){
Deployment_Node(browser, "Web Browser", "Google Chrome, Mozilla Firefox,<br/> Apple Safari or Microsoft Edge"){
Container(spa, "Single Page Application", "JavaScript and Angular", "Provides all of the Internet Banking functionality to customers via their web browser.")
}
}
Deployment_Node(plc, "Big Bank plc", "Big Bank plc data center"){
Deployment_Node(dn, "bigbank-api*** x8", "Ubuntu 16.04 LTS"){
Deployment_Node(apache, "Apache Tomcat", "Apache Tomcat 8.x"){
Container(api, "API Application", "Java and Spring MVC", "Provides Internet Banking functionality via a JSON/HTTPS API.")
}
}
Deployment_Node(bb2, "bigbank-web*** x4", "Ubuntu 16.04 LTS"){
Deployment_Node(apache2, "Apache Tomcat", "Apache Tomcat 8.x"){
Container(web, "Web Application", "Java and Spring MVC", "Delivers the static content and the Internet Banking single page application.")
}
}
Deployment_Node(bigbankdb01, "bigbank-db01", "Ubuntu 16.04 LTS"){
Deployment_Node(oracle, "Oracle - Primary", "Oracle 12c"){
ContainerDb(db, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
}
}
Deployment_Node(bigbankdb02, "bigbank-db02", "Ubuntu 16.04 LTS") {
Deployment_Node(oracle2, "Oracle - Secondary", "Oracle 12c") {
ContainerDb(db2, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
}
}
}
Rel(mobile, api, "Makes API calls to", "json/HTTPS")
Rel(spa, api, "Makes API calls to", "json/HTTPS")
Rel_U(web, spa, "Delivers to the customer's web browser")
Rel(api, db, "Reads from and writes to", "JDBC")
Rel(api, db2, "Reads from and writes to", "JDBC")
Rel_R(db, db2, "Replicates data to")
UpdateRelStyle(spa, api, $offsetY="-40")
UpdateRelStyle(web, spa, $offsetY="-40")
UpdateRelStyle(api, db, $offsetY="-20", $offsetX="5")
UpdateRelStyle(api, db2, $offsetX="-40", $offsetY="-20")
UpdateRelStyle(db, db2, $offsetY="-10")
```
<!--- cspell:ignore bigbank bigbankdb techn mbsfacade --->
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,72 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/configuration.md](../../packages/mermaid/src/docs/config/configuration.md).
# Configuration
When mermaid starts, configuration is extracted to determine a configuration to be used for a diagram. There are 3 sources for configuration:
- The default configuration
- Overrides at the site level are set by the initialize call, and will be applied to all diagrams in the site/app. The term for this is the **siteConfig**.
- Frontmatter (v10.5.0+) - diagram authors can update selected configuration parameters in the frontmatter of the diagram. These are applied to the render config.
- Directives (Deprecated by Frontmatter) - diagram authors can update selected configuration parameters directly in the diagram code via directives. These are applied to the render config.
**The render config** is configuration that is used when rendering by applying these configurations.
## Frontmatter config
The entire mermaid configuration (except the secure configs) can be overridden by the diagram author in the frontmatter of the diagram. The frontmatter is a YAML block at the top of the diagram.
```mermaid-example
---
title: Hello Title
config:
theme: base
themeVariables:
primaryColor: "#00ff00"
---
flowchart
Hello --> World
```
```mermaid
---
title: Hello Title
config:
theme: base
themeVariables:
primaryColor: "#00ff00"
---
flowchart
Hello --> World
```
## Theme configuration
## Starting mermaid
```mermaid-example
sequenceDiagram
Site->>mermaid: initialize
Site->>mermaid: content loaded
mermaid->>mermaidAPI: init
```
```mermaid
sequenceDiagram
Site->>mermaid: initialize
Site->>mermaid: content loaded
mermaid->>mermaidAPI: init
```
## Initialize
The initialize call is applied **only once**. It is called by the site integrator in order to override the default configuration at a site level.
## configApi.reset
This method resets the configuration for a diagram to the overall site configuration, which is the configuration provided by the site integrator. Before each rendering of a diagram, reset is called at the very beginning.
@@ -0,0 +1,342 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/directives.md](../../packages/mermaid/src/docs/config/directives.md).
# Directives
> **Warning**
> Directives are deprecated from v10.5.0. Please use the `config` key in frontmatter to pass configuration. See [Configuration](./configuration.md) for more details.
## Directives
Directives give a diagram author the capability to alter the appearance of a diagram before rendering by changing the applied configuration.
The significance of having directives is that you have them available while writing the diagram, and can modify the default global and diagram-specific configurations. So, directives are applied on top of the default configuration. The beauty of directives is that you can use them to alter configuration settings for a specific diagram, i.e. at an individual level.
While directives allow you to change most of the default configuration settings, there are some that are not available, for security reasons. Also, you have the _option to define the set of configurations_ that you wish to allow diagram authors to override with directives.
## Types of Directives options
Mermaid basically supports two types of configuration options to be overridden by directives.
1. _General/Top Level configurations_ : These are the configurations that are available and applied to all the diagram. **Some of the most important top-level** configurations are:
- theme
- fontFamily
- logLevel
- securityLevel
- startOnLoad
- secure
2. _Diagram-specific configurations_ : These are the configurations that are available and applied to a specific diagram. For each diagram there are specific configuration that will alter how that particular diagram looks and behaves.
For example, `mirrorActors` is a configuration that is specific to the `SequenceDiagram` and alters whether the actors are mirrored or not. So this config is available only for the `SequenceDiagram` type.
**NOTE:** Not all configuration options are listed here. To get hold of all the configuration options, please refer to the [defaultConfig.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/defaultConfig.ts) in the source code.
> **Note**
> We plan to publish a complete list of top-level configurations & diagram-specific configurations with their possible values in the docs soon.
## Declaring directives
Now that we have defined the types of configurations that are available, we can learn how to declare directives.
A directive always starts and ends with `%%` signs with directive text in between, like `%% {directive_text} %%`.
Here the structure of a directive text is like a nested key-value pair map or a JSON object with root being _init_. Where all the general configurations are defined in the top level, and all the diagram specific configurations are defined one level deeper with diagram type as key/root for that section.
The following code snippet shows the structure of a directive:
```
%%{
init: {
"theme": "dark",
"fontFamily": "monospace",
"logLevel": "info",
"htmlLabels": true,
"flowchart": {
"curve": "linear"
},
"sequence": {
"mirrorActors": true
}
}
}%%
```
You can also define the directives in a single line, like this:
```
%%{init: { **insert configuration options here** } }%%
```
For example, the following code snippet:
```
%%{init: { "sequence": { "mirrorActors":false }}}%%
```
**Notes:**
The JSON object that is passed as {**argument**} must be valid key value pairs and encased in quotation marks or it will be ignored.
Valid Key Value pairs can be found in config.
Example with a simple graph:
```mermaid-example
%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%%
graph LR
A-->B
```
```mermaid
%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%%
graph LR
A-->B
```
Here the directive declaration will set the `logLevel` to `debug` and the `theme` to `dark` for a rendered mermaid diagram, changing the appearance of the diagram itself.
Note: You can use 'init' or 'initialize' as both are acceptable as init directives. Also note that `%%init%%` and `%%initialize%%` directives will be grouped together after they are parsed.
```mermaid-example
%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%%
%%{initialize: { 'logLevel': 'fatal', "theme":'dark', 'startOnLoad': true } }%%
...
```
```mermaid
%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%%
%%{initialize: { 'logLevel': 'fatal', "theme":'dark', 'startOnLoad': true } }%%
...
```
For example, parsing the above generates a single `%%init%%` JSON object below, combining the two directives and carrying over the last value given for `loglevel`:
```json
{
"logLevel": "fatal",
"theme": "dark",
"startOnLoad": true
}
```
This will then be sent to `mermaid.initialize(...)` for rendering.
## Directive Examples
Now that the concept of directives has been explained, let us see some more examples of directive usage:
### Changing theme via directive
The following code snippet changes `theme` to `forest`:
`%%{init: { "theme": "forest" } }%%`
Possible theme values are: `default`, `base`, `dark`, `forest` and `neutral`.
Default Value is `default`.
Example:
```mermaid-example
%%{init: { "theme": "forest" } }%%
graph TD
A(Forest) --> B[/Another/]
A --> C[End]
subgraph section
B
C
end
```
```mermaid
%%{init: { "theme": "forest" } }%%
graph TD
A(Forest) --> B[/Another/]
A --> C[End]
subgraph section
B
C
end
```
### Changing fontFamily via directive
The following code snippet changes fontFamily to Trebuchet MS, Verdana, Arial, Sans-Serif:
`%%{init: { "fontFamily": "Trebuchet MS, Verdana, Arial, Sans-Serif" } }%%`
Example:
```mermaid-example
%%{init: { "fontFamily": "Trebuchet MS, Verdana, Arial, Sans-Serif" } }%%
graph TD
A(Forest) --> B[/Another/]
A --> C[End]
subgraph section
B
C
end
```
```mermaid
%%{init: { "fontFamily": "Trebuchet MS, Verdana, Arial, Sans-Serif" } }%%
graph TD
A(Forest) --> B[/Another/]
A --> C[End]
subgraph section
B
C
end
```
### Changing logLevel via directive
The following code snippet changes `logLevel` to `2`:
`%%{init: { "logLevel": 2 } }%%`
Possible `logLevel` values are:
- `1` for _debug_,
- `2` for _info_
- `3` for _warn_
- `4` for _error_
- `5` for _only fatal errors_
Default Value is `5`.
Example:
```mermaid-example
%%{init: { "logLevel": 2 } }%%
graph TD
A(Forest) --> B[/Another/]
A --> C[End]
subgraph section
B
C
end
```
```mermaid
%%{init: { "logLevel": 2 } }%%
graph TD
A(Forest) --> B[/Another/]
A --> C[End]
subgraph section
B
C
end
```
### Changing flowchart config via directive
Some common flowchart configurations are:
- ~~_htmlLabels_~~: Deprecated, [prefer setting this at the root level](/config/schema-docs/config#htmllabels).
- _curve_: linear/curve
- _diagramPadding_: number
- _useMaxWidth_: number
For a complete list of flowchart configurations, see [defaultConfig.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/defaultConfig.ts) in the source code.
_Soon we plan to publish a complete list of all diagram-specific configurations updated in the docs._
The following code snippet changes flowchart config:
```
%%{init: { "htmlLabels": true, "flowchart": { "curve": "linear" } } }%%
```
Here we are overriding only the flowchart config, and not the general config, setting `htmlLabels` to `true` and `curve` to `linear`.
> **Warning**
> **Deprecated:** `flowchart.htmlLabels` has been deprecated from (v\<MERMAID_RELEASE_VERSION>+). Use the global `htmlLabels` configuration instead. For example, instead of `"flowchart": { "htmlLabels": true }`, use `"htmlLabels": true` at the top level.
```mermaid-example
%%{init: { "flowchart": { "htmlLabels": true, "curve": "linear" } } }%%
graph TD
A(Forest) --> B[/Another/]
A --> C[End]
subgraph section
B
C
end
```
```mermaid
%%{init: { "flowchart": { "htmlLabels": true, "curve": "linear" } } }%%
graph TD
A(Forest) --> B[/Another/]
A --> C[End]
subgraph section
B
C
end
```
### Changing Sequence diagram config via directive
Some common sequence diagram configurations are:
- _width_: number
- _height_: number
- _messageAlign_: left, center, right
- _mirrorActors_: boolean
- _useMaxWidth_: boolean
- _rightAngles_: boolean
- _showSequenceNumbers_: boolean
- _wrap_: boolean
For a complete list of sequence diagram configurations, see [defaultConfig.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/defaultConfig.ts) in the source code.
_Soon we plan to publish a complete list of all diagram-specific configurations updated in the docs._
So, `wrap` by default has a value of `false` for sequence diagrams.
Let us see an example:
```mermaid-example
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
Alice->Bob: Good.
Bob->Alice: Cool
```
```mermaid
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
Alice->Bob: Good.
Bob->Alice: Cool
```
Now let us enable wrap for sequence diagrams.
The following code snippet changes sequence diagram config for `wrap` to `true`:
`%%{init: { "sequence": { "wrap": true} } }%%`
By applying that snippet to the diagram above, `wrap` will be enabled:
```mermaid-example
%%{init: { "sequence": { "wrap": true, "width":300 } } }%%
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
Alice->Bob: Good.
Bob->Alice: Cool
```
```mermaid
%%{init: { "sequence": { "wrap": true, "width":300 } } }%%
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
Alice->Bob: Good.
Bob->Alice: Cool
```
@@ -0,0 +1,40 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/layouts.md](../../packages/mermaid/src/docs/config/layouts.md).
# Layouts
This page lists the available layout algorithms supported in Mermaid diagrams.
## Supported Layouts
- **elk**: [ELK (Eclipse Layout Kernel)](https://www.eclipse.org/elk/)
- **tidy-tree**: Tidy tree layout for hierarchical diagrams [Tidy Tree Configuration](/config/tidy-tree)
- **cose-bilkent**: Cose Bilkent layout for force-directed graphs
- **dagre**: Dagre layout for layered graphs
## How to Use
You can specify the layout in your diagram's YAML config or initialization options. For example:
```mermaid-example
---
config:
layout: elk
---
graph TD;
A-->B;
B-->C;
```
```mermaid
---
config:
layout: elk
---
graph TD;
A-->B;
B-->C;
```
@@ -0,0 +1,96 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/math.md](../../packages/mermaid/src/docs/config/math.md).
# Math Configuration (v10.9.0+)
Mermaid supports rendering mathematical expressions through the [KaTeX](https://katex.org/) typesetter.
## Usage
To render math within a diagram, surround the mathematical expression with the `$$` delimiter.
Note that at the moment, the only supported diagrams are below:
### Flowcharts
```mermaid-example
graph LR
A["$$x^2$$"] -->|"$$\sqrt{x+3}$$"| B("$$\frac{1}{2}$$")
A -->|"$$\overbrace{a+b+c}^{\text{note}}$$"| C("$$\pi r^2$$")
B --> D("$$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$$")
C --> E("$$x(t)=c_1\begin{bmatrix}-\cos{t}+\sin{t}\\ 2\cos{t} \end{bmatrix}e^{2t}$$")
```
```mermaid
graph LR
A["$$x^2$$"] -->|"$$\sqrt{x+3}$$"| B("$$\frac{1}{2}$$")
A -->|"$$\overbrace{a+b+c}^{\text{note}}$$"| C("$$\pi r^2$$")
B --> D("$$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$$")
C --> E("$$x(t)=c_1\begin{bmatrix}-\cos{t}+\sin{t}\\ 2\cos{t} \end{bmatrix}e^{2t}$$")
```
### Sequence
```mermaid-example
sequenceDiagram
autonumber
participant 1 as $$\alpha$$
participant 2 as $$\beta$$
1->>2: Solve: $$\sqrt{2+2}$$
2-->>1: Answer: $$2$$
Note right of 2: $$\sqrt{2+2}=\sqrt{4}=2$$
```
```mermaid
sequenceDiagram
autonumber
participant 1 as $$\alpha$$
participant 2 as $$\beta$$
1->>2: Solve: $$\sqrt{2+2}$$
2-->>1: Answer: $$2$$
Note right of 2: $$\sqrt{2+2}=\sqrt{4}=2$$
```
## Legacy Support
By default, MathML is used for rendering mathematical expressions. If you have users on [unsupported browsers](https://caniuse.com/?search=mathml), `legacyMathML` can be set in the config to fall back to CSS rendering. Note that **you must provide KaTeX's stylesheets on your own** as they do not come bundled with Mermaid.
Example with legacy mode enabled (the latest version of KaTeX's stylesheet can be found on their [docs](https://katex.org/docs/browser.html)):
```html
<!doctype html>
<!-- KaTeX requires the use of the HTML5 doctype. Without it, KaTeX may not render properly -->
<html lang="en">
<head>
<!-- Please ensure the stylesheet's version matches with the KaTeX version in your package-lock -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@{version_number}/dist/katex.min.css"
integrity="sha384-{hash}"
crossorigin="anonymous"
/>
</head>
<body>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
legacyMathML: true,
});
</script>
</body>
</html>
```
## Handling Rendering Differences
Due to differences between default fonts across operating systems and browser's MathML implementations, inconsistent results can be seen across platforms. If having consistent results are important, or the most optimal rendered results are desired, `forceLegacyMathML` can be enabled in the config.
This option will always use KaTeX's stylesheet instead of only when MathML is not supported (as with `legacyMathML`). Note that only `forceLegacyMathML` needs to be set.
If including KaTeX's stylesheet is not a concern, enabling this option is recommended to avoid scenarios where no MathML implementation within a browser provides the desired output (as seen below).
![Image showing differences between Browsers](img/mathMLDifferences.png)
@@ -0,0 +1,246 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/theming.md](../../packages/mermaid/src/docs/config/theming.md).
# Theme Configuration
Dynamic and integrated theme configuration was introduced in Mermaid version 8.7.0.
Themes can now be customized at the site-wide level, or on individual Mermaid diagrams. For site-wide theme customization, the `initialize` call is used. For diagram specific customization, frontmatter config is used.
## Available Themes
1. [**default**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-default.js) - This is the default theme for all diagrams.
2. [**neutral**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-neutral.js) - This theme is great for black and white documents that will be printed.
3. [**dark**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-dark.js) - This theme goes well with dark-colored elements or dark-mode.
4. [**forest**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-forest.js) - This theme contains shades of green.
5. [**base**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-base.js) - This is the only theme that can be modified. Use this theme as the base for customizations.
## Site-wide Theme
To customize themes site-wide, call the `initialize` method on the `mermaid`.
Example of `initialize` call setting `theme` to `base`:
```javascript
mermaid.initialize({
securityLevel: 'loose',
theme: 'base',
});
```
## Diagram-specific Themes
To customize the theme of an individual diagram, use frontmatter config.
Example of frontmatter config setting the `theme` to `forest`:
```mermaid-example
---
config:
theme: 'forest'
---
graph TD
a --> b
```
```mermaid
---
config:
theme: 'forest'
---
graph TD
a --> b
```
> **Reminder**: the only theme that can be customized is the `base` theme. The following section covers how to use `themeVariables` for customizations.
## Customizing Themes with `themeVariables`
To make a custom theme, modify `themeVariables` via frontmatter config.
You will need to use the [base](#available-themes) theme as it is the only modifiable theme.
| Parameter | Description | Type | Properties |
| -------------- | ---------------------------------- | ------ | ----------------------------------------------------------------------------------- |
| themeVariables | Modifiable with frontmatter config | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables)) |
Example of modifying `themeVariables` using frontmatter config:
```mermaid-example
---
config:
theme: 'base'
themeVariables:
primaryColor: '#BB2528'
primaryTextColor: '#fff'
primaryBorderColor: '#7C0000'
lineColor: '#F8B229'
secondaryColor: '#006100'
tertiaryColor: '#fff'
---
graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
B --> G[/Another/]
C ==>|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
subgraph section
C
D
E
F
G
end
```
```mermaid
---
config:
theme: 'base'
themeVariables:
primaryColor: '#BB2528'
primaryTextColor: '#fff'
primaryBorderColor: '#7C0000'
lineColor: '#F8B229'
secondaryColor: '#006100'
tertiaryColor: '#fff'
---
graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
B --> G[/Another/]
C ==>|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
subgraph section
C
D
E
F
G
end
```
## Color and Color Calculation
To ensure diagram readability, the default value of certain variables is calculated or derived from other variables. For example, `primaryBorderColor` is derived from the `primaryColor` variable. So if the `primaryColor` variable is customized, Mermaid will adjust `primaryBorderColor` automatically. Adjustments can mean a color inversion, a hue change, a darkening/lightening by 10%, etc.
The theming engine will only recognize hex colors and not color names. So, the value `#ff0000` will work, but `red` will not.
## Theme Variables
| Variable | Default value | Description |
| -------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| darkMode | false | Affects how derived colors are calculated. Set value to `true` for dark mode. |
| background | #f4f4f4 | Used to calculate color for items that should either be background colored or contrasting to the background |
| fontFamily | trebuchet ms, verdana, arial | Font family for diagram text |
| fontSize | 16px | Font size in pixels |
| primaryColor | #fff4dd | Color to be used as background in nodes, other colors will be derived from this |
| primaryTextColor | calculated from darkMode #ddd/#333 | Color to be used as text color in nodes using `primaryColor` |
| secondaryColor | calculated from primaryColor | |
| primaryBorderColor | calculated from primaryColor | Color to be used as border in nodes using `primaryColor` |
| secondaryBorderColor | calculated from secondaryColor | Color to be used as border in nodes using `secondaryColor` |
| secondaryTextColor | calculated from secondaryColor | Color to be used as text color in nodes using `secondaryColor` |
| tertiaryColor | calculated from primaryColor | |
| tertiaryBorderColor | calculated from tertiaryColor | Color to be used as border in nodes using `tertiaryColor` |
| tertiaryTextColor | calculated from tertiaryColor | Color to be used as text color in nodes using `tertiaryColor` |
| noteBkgColor | #fff5ad | Color used as background in notes |
| noteTextColor | #333 | Text color in note rectangles |
| noteBorderColor | calculated from noteBkgColor | Border color in note rectangles |
| lineColor | calculated from background | |
| textColor | calculated from primaryTextColor | Text in diagram over the background for instance text on labels and on signals in sequence diagram or the title in Gantt diagram |
| mainBkg | calculated from primaryColor | Background in flowchart objects like rects/circles, class diagram classes, sequence diagram etc |
| errorBkgColor | tertiaryColor | Color for syntax error message |
| errorTextColor | tertiaryTextColor | Color for syntax error message |
## Flowchart Variables
| Variable | Default value | Description |
| ------------------- | ------------------------------ | --------------------------- |
| nodeBorder | primaryBorderColor | Node Border Color |
| clusterBkg | tertiaryColor | Background in subgraphs |
| clusterBorder | tertiaryBorderColor | Cluster Border Color |
| defaultLinkColor | lineColor | Link Color |
| titleColor | tertiaryTextColor | Title Color |
| edgeLabelBackground | calculated from secondaryColor | |
| nodeTextColor | primaryTextColor | Color for text inside Nodes |
## Sequence Diagram Variables
| Variable | Default value | Description |
| --------------------- | ------------------------------ | --------------------------- |
| actorBkg | mainBkg | Actor Background Color |
| actorBorder | primaryBorderColor | Actor Border Color |
| actorTextColor | primaryTextColor | Actor Text Color |
| actorLineColor | actorBorder | Actor Line Color |
| signalColor | textColor | Signal Color |
| signalTextColor | textColor | Signal Text Color |
| labelBoxBkgColor | actorBkg | Label Box Background Color |
| labelBoxBorderColor | actorBorder | Label Box Border Color |
| labelTextColor | actorTextColor | Label Text Color |
| loopTextColor | actorTextColor | Loop Text Color |
| activationBorderColor | calculated from secondaryColor | Activation Border Color |
| activationBkgColor | secondaryColor | Activation Background Color |
| sequenceNumberColor | calculated from lineColor | Sequence Number Color |
## Pie Diagram Variables
| Variable | Default value | Description |
| ------------------- | ------------------------------ | ------------------------------------------ |
| pie1 | primaryColor | Fill for 1st section in pie diagram |
| pie2 | secondaryColor | Fill for 2nd section in pie diagram |
| pie3 | calculated from tertiary | Fill for 3rd section in pie diagram |
| pie4 | calculated from primaryColor | Fill for 4th section in pie diagram |
| pie5 | calculated from secondaryColor | Fill for 5th section in pie diagram |
| pie6 | calculated from tertiaryColor | Fill for 6th section in pie diagram |
| pie7 | calculated from primaryColor | Fill for 7th section in pie diagram |
| pie8 | calculated from primaryColor | Fill for 8th section in pie diagram |
| pie9 | calculated from primaryColor | Fill for 9th section in pie diagram |
| pie10 | calculated from primaryColor | Fill for 10th section in pie diagram |
| pie11 | calculated from primaryColor | Fill for 11th section in pie diagram |
| pie12 | calculated from primaryColor | Fill for 12th section in pie diagram |
| pieTitleTextSize | 25px | Title text size |
| pieTitleTextColor | taskTextDarkColor | Title text color |
| pieSectionTextSize | 17px | Text size of individual section labels |
| pieSectionTextColor | textColor | Text color of individual section labels |
| pieLegendTextSize | 17px | Text size of labels in diagram legend |
| pieLegendTextColor | taskTextDarkColor | Text color of labels in diagram legend |
| pieStrokeColor | black | Border color of individual pie sections |
| pieStrokeWidth | 2px | Border width of individual pie sections |
| pieOuterStrokeWidth | 2px | Border width of pie diagram's outer circle |
| pieOuterStrokeColor | black | Border color of pie diagram's outer circle |
| pieOpacity | 0.7 | Opacity of individual pie sections |
## State Colors
| Variable | Default value | Description |
| ------------- | ---------------- | -------------------------------------------- |
| labelColor | primaryTextColor | |
| altBackground | tertiaryColor | Used for background in deep composite states |
## Class Colors
| Variable | Default value | Description |
| --------- | ------------- | ------------------------------- |
| classText | textColor | Color of Text in class diagrams |
## User Journey Colors
| Variable | Default value | Description |
| --------- | ------------------------------ | --------------------------------------- |
| fillType0 | primaryColor | Fill for 1st section in journey diagram |
| fillType1 | secondaryColor | Fill for 2nd section in journey diagram |
| fillType2 | calculated from primaryColor | Fill for 3rd section in journey diagram |
| fillType3 | calculated from secondaryColor | Fill for 4th section in journey diagram |
| fillType4 | calculated from primaryColor | Fill for 5th section in journey diagram |
| fillType5 | calculated from secondaryColor | Fill for 6th section in journey diagram |
| fillType6 | calculated from primaryColor | Fill for 7th section in journey diagram |
| fillType7 | calculated from secondaryColor | Fill for 8th section in journey diagram |
@@ -0,0 +1,89 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/tidy-tree.md](../../packages/mermaid/src/docs/config/tidy-tree.md).
# Tidy-tree Layout
The **tidy-tree** layout arranges nodes in a hierarchical, tree-like structure. It is especially useful for diagrams where parent-child relationships are important, such as mindmaps.
## Features
- Organizes nodes in a tidy, non-overlapping tree
- Ideal for mindmaps and hierarchical data
- Automatically adjusts spacing for readability
## Example Usage
```mermaid-example
---
config:
layout: tidy-tree
---
mindmap
root((mindmap is a long thing))
A
B
C
D
```
```mermaid
---
config:
layout: tidy-tree
---
mindmap
root((mindmap is a long thing))
A
B
C
D
```
```mermaid-example
---
config:
layout: tidy-tree
---
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness<br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
```
```mermaid
---
config:
layout: tidy-tree
---
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness<br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
```
## Note
- Currently, tidy-tree is primarily supported for mindmap diagrams.
@@ -0,0 +1,670 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md](../../packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md).
# Entity Relationship Diagrams
> An entityrelationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types) [Wikipedia](https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model).
Note that practitioners of ER modelling almost always refer to _entity types_ simply as _entities_. For example the `CUSTOMER` entity _type_ would be referred to simply as the `CUSTOMER` entity. This is so common it would be inadvisable to do anything else, but technically an entity is an abstract _instance_ of an entity type, and this is what an ER diagram shows - abstract instances, and the relationships between them. This is why entities are always named using singular nouns.
Mermaid can render ER diagrams
```mermaid-example
---
title: Order example
---
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
```
```mermaid
---
title: Order example
---
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
```
Entity names are often capitalised, although there is no accepted standard on this, and it is not required in Mermaid.
Relationships between entities are represented by lines with end markers representing cardinality. Mermaid uses the most popular crow's foot notation. The crow's foot intuitively conveys the possibility of many instances of the entity that it connects to.
ER diagrams can be used for various purposes, ranging from abstract logical models devoid of any implementation details, through to physical models of relational database tables. It can be useful to include attribute definitions on ER diagrams to aid comprehension of the purpose and meaning of entities. These do not necessarily need to be exhaustive; often a small subset of attributes is enough. Mermaid allows them to be defined in terms of their _type_ and _name_.
```mermaid-example
erDiagram
CUSTOMER ||--o{ ORDER : places
CUSTOMER {
string name
string custNumber
string sector
}
ORDER ||--|{ LINE-ITEM : contains
ORDER {
int orderNumber
string deliveryAddress
}
LINE-ITEM {
string productCode
int quantity
float pricePerUnit
}
```
```mermaid
erDiagram
CUSTOMER ||--o{ ORDER : places
CUSTOMER {
string name
string custNumber
string sector
}
ORDER ||--|{ LINE-ITEM : contains
ORDER {
int orderNumber
string deliveryAddress
}
LINE-ITEM {
string productCode
int quantity
float pricePerUnit
}
```
When including attributes on ER diagrams, you must decide whether to include foreign keys as attributes. This probably depends on how closely you are trying to represent relational table structures. If your diagram is a _logical_ model which is not meant to imply a relational implementation, then it is better to leave these out because the associative relationships already convey the way that entities are associated. For example, a JSON data structure can implement a one-to-many relationship without the need for foreign key properties, using arrays. Similarly an object-oriented programming language may use pointers or references to collections. Even for models that are intended for relational implementation, you might decide that inclusion of foreign key attributes duplicates information already portrayed by the relationships, and does not add meaning to entities. Ultimately, it's your choice.
## Syntax
### Entities and Relationships
Mermaid syntax for ER diagrams is compatible with PlantUML, with an extension to label the relationship. Each statement consists of the following parts:
```
<first-entity> [<relationship> <second-entity> : <relationship-label>]
```
Where:
- `first-entity` is the name of an entity. Names support any unicode characters and can include spaces if surrounded by double quotes (e.g. "name with space").
- `relationship` describes the way that both entities inter-relate. See below.
- `second-entity` is the name of the other entity.
- `relationship-label` describes the relationship from the perspective of the first entity.
For example:
```
PROPERTY ||--|{ ROOM : contains
```
This statement can be read as _a property contains one or more rooms, and a room is part of one and only one property_. You can see that the label here is from the first entity's perspective: a property contains a room, but a room does not contain a property. When considered from the perspective of the second entity, the equivalent label is usually very easy to infer. (Some ER diagrams label relationships from both perspectives, but this is not supported here, and is usually superfluous).
Only the `first-entity` part of a statement is mandatory. This makes it possible to show an entity with no relationships, which can be useful during iterative construction of diagrams. If any other parts of a statement are specified, then all parts are mandatory.
#### Unicode text
Entity names, relationships, and attributes all support unicode text.
```mermaid-example
erDiagram
"This ❤ Unicode"
```
```mermaid
erDiagram
"This ❤ Unicode"
```
#### Markdown formatting
Markdown formatting and text is also supported.
```mermaid-example
erDiagram
"This **is** _Markdown_"
```
```mermaid
erDiagram
"This **is** _Markdown_"
```
### Relationship Syntax
The `relationship` part of each statement can be broken down into three sub-components:
- the cardinality of the first entity with respect to the second
- whether the relationship confers identity on a 'child' entity
- the cardinality of the second entity with respect to the first
Cardinality is a property that describes how many elements of another entity can be related to the entity in question. In the above example a `PROPERTY` can have one or more `ROOM` instances associated to it, whereas a `ROOM` can only be associated with one `PROPERTY`. In each cardinality marker there are two characters. The outermost character represents a maximum value, and the innermost character represents a minimum value. The table below summarises possible cardinalities.
| Value (left) | Value (right) | Meaning |
| :----------: | :-----------: | ----------------------------- |
| `\|o` | `o\|` | Zero or one |
| `\|\|` | `\|\|` | Exactly one |
| `}o` | `o{` | Zero or more (no upper limit) |
| `}\|` | `\|{` | One or more (no upper limit) |
**Aliases**
| Value (left) | Value (right) | Alias for |
| :----------: | :-----------: | ------------ |
| one or zero | one or zero | Zero or one |
| zero or one | zero or one | Zero or one |
| one or more | one or more | One or more |
| one or many | one or many | One or more |
| many(1) | many(1) | One or more |
| 1+ | 1+ | One or more |
| zero or more | zero or more | Zero or more |
| zero or many | zero or many | Zero or more |
| many(0) | many(0) | Zero or more |
| 0+ | 0+ | Zero or more |
| only one | only one | Exactly one |
| 1 | 1 | Exactly one |
### Identification
Relationships may be classified as either _identifying_ or _non-identifying_ and these are rendered with either solid or dashed lines respectively. This is relevant when one of the entities in question cannot have independent existence without the other. For example a firm that insures people to drive cars might need to store data on `NAMED-DRIVER`s. In modelling this we might start out by observing that a `CAR` can be driven by many `PERSON` instances, and a `PERSON` can drive many `CAR`s - both entities can exist without the other, so this is a non-identifying relationship that we might specify in Mermaid as: `PERSON }|..|{ CAR : "driver"`. Note the two dots in the middle of the relationship that will result in a dashed line being drawn between the two entities. But when this many-to-many relationship is resolved into two one-to-many relationships, we observe that a `NAMED-DRIVER` cannot exist without both a `PERSON` and a `CAR` - the relationships become identifying and would be specified using hyphens, which translate to a solid line:
| Value | Alias for |
| :---: | :---------------: |
| -- | _identifying_ |
| .. | _non-identifying_ |
**Aliases**
| Value | Alias for |
| :-----------: | :---------------: |
| to | _identifying_ |
| optionally to | _non-identifying_ |
```mermaid-example
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
PERSON }o..o{ NAMED-DRIVER : is
```
```mermaid
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
PERSON }o..o{ NAMED-DRIVER : is
```
```mermaid-example
erDiagram
CAR 1 to zero or more NAMED-DRIVER : allows
PERSON many(0) optionally to 0+ NAMED-DRIVER : is
```
```mermaid
erDiagram
CAR 1 to zero or more NAMED-DRIVER : allows
PERSON many(0) optionally to 0+ NAMED-DRIVER : is
```
### Attributes
Attributes can be defined for entities by specifying the entity name followed by a block containing multiple `type name` pairs, where a block is delimited by an opening `{` and a closing `}`. The attributes are rendered inside the entity boxes. For example:
```mermaid-example
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
CAR {
string registrationNumber
string make
string model
}
PERSON ||--o{ NAMED-DRIVER : is
PERSON {
string firstName
string lastName
int age
}
```
```mermaid
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
CAR {
string registrationNumber
string make
string model
}
PERSON ||--o{ NAMED-DRIVER : is
PERSON {
string firstName
string lastName
int age
}
```
The `type` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. The `name` values follow a similar format to `type`, but may start with an asterisk as another option to indicate an attribute is a primary key. Other than that, there are no restrictions, and there is no implicit set of valid data types.
### Entity Name Aliases
An alias can be added to an entity using square brackets. If provided, the alias will be showed in the diagram instead of the entity name. Alias names follow all of the same rules as entity names.
```mermaid-example
erDiagram
p[Person] {
string firstName
string lastName
}
a["Customer Account"] {
string email
}
p ||--o| a : has
```
```mermaid
erDiagram
p[Person] {
string firstName
string lastName
}
a["Customer Account"] {
string email
}
p ||--o| a : has
```
#### Attribute Keys and Comments
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key (markdown formatting and unicode is not supported for keys). To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`). A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
```mermaid-example
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
CAR {
string registrationNumber PK
string make
string model
string[] parts
}
PERSON ||--o{ NAMED-DRIVER : is
PERSON {
string driversLicense PK "The license #"
string(99) firstName "Only 99 characters are allowed"
string lastName
string phone UK
int age
}
NAMED-DRIVER {
string carRegistrationNumber PK, FK
string driverLicence PK, FK
}
MANUFACTURER only one to zero or more CAR : makes
```
```mermaid
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
CAR {
string registrationNumber PK
string make
string model
string[] parts
}
PERSON ||--o{ NAMED-DRIVER : is
PERSON {
string driversLicense PK "The license #"
string(99) firstName "Only 99 characters are allowed"
string lastName
string phone UK
int age
}
NAMED-DRIVER {
string carRegistrationNumber PK, FK
string driverLicence PK, FK
}
MANUFACTURER only one to zero or more CAR : makes
```
### Direction
The direction statement declares the direction of the diagram.
This declares that the diagram is oriented from top to bottom (`TB`). This can be reversed to be oriented from bottom to top (`BT`).
```mermaid-example
erDiagram
direction TB
CUSTOMER ||--o{ ORDER : places
CUSTOMER {
string name
string custNumber
string sector
}
ORDER ||--|{ LINE-ITEM : contains
ORDER {
int orderNumber
string deliveryAddress
}
LINE-ITEM {
string productCode
int quantity
float pricePerUnit
}
```
```mermaid
erDiagram
direction TB
CUSTOMER ||--o{ ORDER : places
CUSTOMER {
string name
string custNumber
string sector
}
ORDER ||--|{ LINE-ITEM : contains
ORDER {
int orderNumber
string deliveryAddress
}
LINE-ITEM {
string productCode
int quantity
float pricePerUnit
}
```
This declares that the diagram is oriented from left to right (`LR`). This can be reversed to be oriented from right to left (`RL`).
```mermaid-example
erDiagram
direction LR
CUSTOMER ||--o{ ORDER : places
CUSTOMER {
string name
string custNumber
string sector
}
ORDER ||--|{ LINE-ITEM : contains
ORDER {
int orderNumber
string deliveryAddress
}
LINE-ITEM {
string productCode
int quantity
float pricePerUnit
}
```
```mermaid
erDiagram
direction LR
CUSTOMER ||--o{ ORDER : places
CUSTOMER {
string name
string custNumber
string sector
}
ORDER ||--|{ LINE-ITEM : contains
ORDER {
int orderNumber
string deliveryAddress
}
LINE-ITEM {
string productCode
int quantity
float pricePerUnit
}
```
Possible diagram orientations are:
- TB - Top to bottom
- BT - Bottom to top
- RL - Right to left
- LR - Left to right
### Styling a node
It is possible to apply specific styles such as a thicker border or a different background color to a node.
```mermaid-example
erDiagram
id1||--||id2 : label
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
```
```mermaid
erDiagram
id1||--||id2 : label
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
```
It is also possible to attach styles to a list of nodes in one statement:
```
style nodeId1,nodeId2 styleList
```
#### Classes
More convenient than defining the style every time is to define a class of styles and attach this class to the nodes that
should have a different look.
A class definition looks like the example below:
```
classDef className fill:#f9f,stroke:#333,stroke-width:4px
```
It is also possible to define multiple classes in one statement:
```
classDef firstClassName,secondClassName font-size:12pt
```
Attachment of a class to a node is done as per below:
```
class nodeId1 className
```
It is also possible to attach a class to a list of nodes in one statement:
```
class nodeId1,nodeId2 className
```
Multiple classes can be attached at the same time as well:
```
class nodeId1,nodeId2 className1,className2
```
A shorter form of adding a class is to attach the classname to the node using the `:::`operator as per below:
```mermaid-example
erDiagram
direction TB
CAR:::someclass {
string registrationNumber
string make
string model
}
PERSON:::someclass {
string firstName
string lastName
int age
}
HOUSE:::someclass
classDef someclass fill:#f96
```
```mermaid
erDiagram
direction TB
CAR:::someclass {
string registrationNumber
string make
string model
}
PERSON:::someclass {
string firstName
string lastName
int age
}
HOUSE:::someclass
classDef someclass fill:#f96
```
This form can be used when declaring relationships between entities:
```mermaid-example
erDiagram
CAR {
string registrationNumber
string make
string model
}
PERSON {
string firstName
string lastName
int age
}
PERSON:::foo ||--|| CAR : owns
PERSON o{--|| HOUSE:::bar : has
classDef foo stroke:#f00
classDef bar stroke:#0f0
classDef foobar stroke:#00f
```
```mermaid
erDiagram
CAR {
string registrationNumber
string make
string model
}
PERSON {
string firstName
string lastName
int age
}
PERSON:::foo ||--|| CAR : owns
PERSON o{--|| HOUSE:::bar : has
classDef foo stroke:#f00
classDef bar stroke:#0f0
classDef foobar stroke:#00f
```
Similar to the class statement, the shorthand syntax can also apply multiple classes at once:
```
nodeId:::className1,className2
```
### Default class
If a class is named default it will be assigned to all classes without specific class definitions.
```
classDef default fill:#f9f,stroke:#333,stroke-width:4px;
```
> **Note:** Custom styles from style or other class statements take priority and will overwrite the default styles. (e.g. The `default` class gives nodes a background color of pink but the `blue` class will give that node a background color of blue if applied.)
```mermaid-example
erDiagram
CAR {
string registrationNumber
string make
string model
}
PERSON {
string firstName
string lastName
int age
}
PERSON:::foo ||--|| CAR : owns
PERSON o{--|| HOUSE:::bar : has
classDef default fill:#f9f,stroke-width:4px
classDef foo stroke:#f00
classDef bar stroke:#0f0
classDef foobar stroke:#00f
```
```mermaid
erDiagram
CAR {
string registrationNumber
string make
string model
}
PERSON {
string firstName
string lastName
int age
}
PERSON:::foo ||--|| CAR : owns
PERSON o{--|| HOUSE:::bar : has
classDef default fill:#f9f,stroke-width:4px
classDef foo stroke:#f00
classDef bar stroke:#0f0
classDef foobar stroke:#00f
```
## Configuration
### Layout
The layout of the diagram is handled by [`render()`](../config/setup/mermaid/interfaces/Mermaid.md#render). The default layout is dagre.
For larger or more-complex diagrams, you can alternatively apply the ELK (Eclipse Layout Kernel) layout using your YAML frontmatter's `config`. For more information, see [Customizing ELK Layout](../intro/syntax-reference.md#customizing-elk-layout).
```yaml
---
config:
layout: elk
---
```
Your Mermaid code should be similar to the following:
```mermaid-example
---
title: Order example
config:
layout: elk
---
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
```
```mermaid
---
title: Order example
config:
layout: elk
---
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
```
> **Note**
> Note that the site needs to use mermaid version 9.4+ for this to work and have this featured enabled in the lazy-loading configuration.
<!--- cspell:locale en,en-gb --->
@@ -0,0 +1,301 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/examples.md](../../packages/mermaid/src/docs/syntax/examples.md).
# Examples
This page contains a collection of examples of diagrams and charts that can be created through mermaid and its myriad applications.
**If you wish to learn how to support mermaid on your webpage, read the [Beginner's Guide](../config/usage.md?id=usage).**
**If you wish to learn about mermaid's syntax, Read the [Diagram Syntax](../syntax/flowchart.md?id=flowcharts-basic-syntax) section.**
## Basic Pie Chart
```mermaid-example
pie title NETFLIX
"Time spent looking for movie" : 90
"Time spent watching it" : 10
```
```mermaid
pie title NETFLIX
"Time spent looking for movie" : 90
"Time spent watching it" : 10
```
```mermaid-example
pie title What Voldemort doesn't have?
"FRIENDS" : 2
"FAMILY" : 3
"NOSE" : 45
```
```mermaid
pie title What Voldemort doesn't have?
"FRIENDS" : 2
"FAMILY" : 3
"NOSE" : 45
```
## Basic sequence diagram
```mermaid-example
sequenceDiagram
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
Bob-->Alice: Checking with John...
Alice->John: Yes... John, how are you?
```
```mermaid
sequenceDiagram
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
Bob-->Alice: Checking with John...
Alice->John: Yes... John, how are you?
```
## Basic flowchart
```mermaid-example
graph LR
A[Square Rect] -- Link text --> B((Circle))
A --> C(Round Rect)
B --> D{Rhombus}
C --> D
```
```mermaid
graph LR
A[Square Rect] -- Link text --> B((Circle))
A --> C(Round Rect)
B --> D{Rhombus}
C --> D
```
## Larger flowchart with some styling
```mermaid-example
graph TB
sq[Square shape] --> ci((Circle shape))
subgraph A
od>Odd shape]-- Two line<br/>edge comment --> ro
di{Diamond with <br/> line break} -.-> ro(Rounded<br>square<br>shape)
di==>ro2(Rounded square shape)
end
%% Notice that no text in shape are added here instead that is appended further down
e --> od3>Really long text with linebreak<br>in an Odd shape]
%% Comments after double percent signs
e((Inner / circle<br>and some odd <br>special characters)) --> f(,.?!+-*ز)
cyr[Cyrillic]-->cyr2((Circle shape Начало));
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
classDef orange fill:#f96,stroke:#333,stroke-width:4px;
class sq,e green
class di orange
```
```mermaid
graph TB
sq[Square shape] --> ci((Circle shape))
subgraph A
od>Odd shape]-- Two line<br/>edge comment --> ro
di{Diamond with <br/> line break} -.-> ro(Rounded<br>square<br>shape)
di==>ro2(Rounded square shape)
end
%% Notice that no text in shape are added here instead that is appended further down
e --> od3>Really long text with linebreak<br>in an Odd shape]
%% Comments after double percent signs
e((Inner / circle<br>and some odd <br>special characters)) --> f(,.?!+-*ز)
cyr[Cyrillic]-->cyr2((Circle shape Начало));
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
classDef orange fill:#f96,stroke:#333,stroke-width:4px;
class sq,e green
class di orange
```
## SequenceDiagram: Loops, alt and opt
```mermaid-example
sequenceDiagram
loop Daily query
Alice->>Bob: Hello Bob, how are you?
alt is sick
Bob->>Alice: Not so good :(
else is well
Bob->>Alice: Feeling fresh like a daisy
end
opt Extra response
Bob->>Alice: Thanks for asking
end
end
```
```mermaid
sequenceDiagram
loop Daily query
Alice->>Bob: Hello Bob, how are you?
alt is sick
Bob->>Alice: Not so good :(
else is well
Bob->>Alice: Feeling fresh like a daisy
end
opt Extra response
Bob->>Alice: Thanks for asking
end
end
```
## SequenceDiagram: Message to self in loop
```mermaid-example
sequenceDiagram
participant Alice
participant Bob
Alice->>John: Hello John, how are you?
loop HealthCheck
John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts<br/>prevail...
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
```
```mermaid
sequenceDiagram
participant Alice
participant Bob
Alice->>John: Hello John, how are you?
loop HealthCheck
John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts<br/>prevail...
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
```
## Sequence Diagram: Blogging app service communication
```mermaid-example
sequenceDiagram
participant web as Web Browser
participant blog as Blog Service
participant account as Account Service
participant mail as Mail Service
participant db as Storage
Note over web,db: The user must be logged in to submit blog posts
web->>+account: Logs in using credentials
account->>db: Query stored accounts
db->>account: Respond with query result
alt Credentials not found
account->>web: Invalid credentials
else Credentials found
account->>-web: Successfully logged in
Note over web,db: When the user is authenticated, they can now submit new posts
web->>+blog: Submit new post
blog->>db: Store post data
par Notifications
blog--)mail: Send mail to blog subscribers
blog--)db: Store in-site notifications
and Response
blog-->>-web: Successfully posted
end
end
```
```mermaid
sequenceDiagram
participant web as Web Browser
participant blog as Blog Service
participant account as Account Service
participant mail as Mail Service
participant db as Storage
Note over web,db: The user must be logged in to submit blog posts
web->>+account: Logs in using credentials
account->>db: Query stored accounts
db->>account: Respond with query result
alt Credentials not found
account->>web: Invalid credentials
else Credentials found
account->>-web: Successfully logged in
Note over web,db: When the user is authenticated, they can now submit new posts
web->>+blog: Submit new post
blog->>db: Store post data
par Notifications
blog--)mail: Send mail to blog subscribers
blog--)db: Store in-site notifications
and Response
blog-->>-web: Successfully posted
end
end
```
## A commit flow diagram.
```mermaid-example
gitGraph:
commit "Ashish"
branch newbranch
checkout newbranch
commit id:"1111"
commit tag:"test"
checkout main
commit type: HIGHLIGHT
commit
merge newbranch
commit
branch b2
commit
```
```mermaid
gitGraph:
commit "Ashish"
branch newbranch
checkout newbranch
commit id:"1111"
commit tag:"test"
checkout main
commit type: HIGHLIGHT
commit
merge newbranch
commit
branch b2
commit
```
<!--- cspell:ignore Ashish newbranch --->
File diff suppressed because it is too large Load Diff
+708
View File
@@ -0,0 +1,708 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/gantt.md](../../packages/mermaid/src/docs/syntax/gantt.md).
# Gantt diagrams
> A Gantt chart is a type of bar chart, first developed by Karol Adamiecki in 1896, and independently by Henry Gantt in the 1910s, that illustrates a project schedule and the amount of time it would take for any one project to finish. Gantt charts illustrate number of days between the start and finish dates of the terminal elements and summary elements of a project.
## A note to users
Gantt Charts will record each scheduled task as one continuous bar that extends from the left to the right. The x axis represents time and the y records the different tasks and the order in which they are to be completed.
It is important to remember that when a date, day, or collection of dates specific to a task are "excluded", the Gantt Chart will accommodate those changes by extending an equal number of days, towards the right, not by creating a gap inside the task.
As shown here ![](./img/Gantt-excluded-days-within.png)
However, if the excluded dates are between two tasks that are set to start consecutively, the excluded dates will be skipped graphically and left blank, and the following task will begin after the end of the excluded dates.
As shown here ![](./img/Gantt-long-weekend-look.png)
A Gantt chart is useful for tracking the amount of time it would take before a project is finished, but it can also be used to graphically represent "non-working days", with a few tweaks.
Mermaid can render Gantt diagrams as SVG, PNG or a MarkDown link that can be pasted into docs.
```mermaid-example
gantt
title A Gantt Diagram
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :after a1, 20d
section Another
Task in Another :2014-01-12, 12d
another task :24d
```
```mermaid
gantt
title A Gantt Diagram
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :after a1, 20d
section Another
Task in Another :2014-01-12, 12d
another task :24d
```
## Syntax
```mermaid-example
gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram functionality to mermaid
excludes weekends
%% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
Active task :active, des2, 2014-01-09, 3d
Future task : des3, after des2, 5d
Future task2 : des4, after des3, 5d
section Critical tasks
Completed task in the critical line :crit, done, 2014-01-06,24h
Implement parser and jison :crit, done, after des1, 2d
Create tests for parser :crit, active, 3d
Future task in critical line :crit, 5d
Create tests for renderer :2d
Add to mermaid :until isadded
Functionality added :milestone, isadded, 2014-01-25, 0d
section Documentation
Describe gantt syntax :active, a1, after des1, 3d
Add gantt diagram to demo page :after a1 , 20h
Add another diagram to demo page :doc1, after a1 , 48h
section Last section
Describe gantt syntax :after doc1, 3d
Add gantt diagram to demo page :20h
Add another diagram to demo page :48h
```
```mermaid
gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram functionality to mermaid
excludes weekends
%% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
Active task :active, des2, 2014-01-09, 3d
Future task : des3, after des2, 5d
Future task2 : des4, after des3, 5d
section Critical tasks
Completed task in the critical line :crit, done, 2014-01-06,24h
Implement parser and jison :crit, done, after des1, 2d
Create tests for parser :crit, active, 3d
Future task in critical line :crit, 5d
Create tests for renderer :2d
Add to mermaid :until isadded
Functionality added :milestone, isadded, 2014-01-25, 0d
section Documentation
Describe gantt syntax :active, a1, after des1, 3d
Add gantt diagram to demo page :after a1 , 20h
Add another diagram to demo page :doc1, after a1 , 48h
section Last section
Describe gantt syntax :after doc1, 3d
Add gantt diagram to demo page :20h
Add another diagram to demo page :48h
```
Tasks are by default sequential. A task start date defaults to the end date of the preceding task.
A colon, `:`, separates the task title from its metadata.
Metadata items are separated by a comma, `,`. Valid tags are `active`, `done`, `crit`, and `milestone`. Tags are optional, but if used, they must be specified first.
After processing the tags, the remaining metadata items are interpreted as follows:
1. If a single item is specified, it determines when the task ends. It can either be a specific date/time or a duration. If a duration is specified, it is added to the start date of the task to determine the end date of the task, taking into account any exclusions.
2. If two items are specified, the last item is interpreted as in the previous case. The first item can either specify an explicit start date/time (in the format specified by `dateFormat`) or reference another task using `after <otherTaskID> [[otherTaskID2 [otherTaskID3]]...]`. In the latter case, the start date of the task will be set according to the latest end date of any referenced task.
3. If three items are specified, the last two will be interpreted as in the previous case. The first item will denote the ID of the task, which can be referenced using the `later <taskID>` syntax.
| Metadata syntax | Start date | End date | ID |
| ---------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------- | -------- |
| `<taskID>, <startDate>, <endDate>` | `startdate` as interpreted using `dateformat` | `endDate` as interpreted using `dateformat` | `taskID` |
| `<taskID>, <startDate>, <length>` | `startdate` as interpreted using `dateformat` | Start date + `length` | `taskID` |
| `<taskID>, after <otherTaskId>, <endDate>` | End date of previously specified task `otherTaskID` | `endDate` as interpreted using `dateformat` | `taskID` |
| `<taskID>, after <otherTaskId>, <length>` | End date of previously specified task `otherTaskID` | Start date + `length` | `taskID` |
| `<taskID>, <startDate>, until <otherTaskId>` | `startdate` as interpreted using `dateformat` | Start date of previously specified task `otherTaskID` | `taskID` |
| `<taskID>, after <otherTaskId>, until <otherTaskId>` | End date of previously specified task `otherTaskID` | Start date of previously specified task `otherTaskID` | `taskID` |
| `<startDate>, <endDate>` | `startdate` as interpreted using `dateformat` | `enddate` as interpreted using `dateformat` | n/a |
| `<startDate>, <length>` | `startdate` as interpreted using `dateformat` | Start date + `length` | n/a |
| `after <otherTaskID>, <endDate>` | End date of previously specified task `otherTaskID` | `enddate` as interpreted using `dateformat` | n/a |
| `after <otherTaskID>, <length>` | End date of previously specified task `otherTaskID` | Start date + `length` | n/a |
| `<startDate>, until <otherTaskId>` | `startdate` as interpreted using `dateformat` | Start date of previously specified task `otherTaskID` | n/a |
| `after <otherTaskId>, until <otherTaskId>` | End date of previously specified task `otherTaskID` | Start date of previously specified task `otherTaskID` | n/a |
| `<endDate>` | End date of preceding task | `enddate` as interpreted using `dateformat` | n/a |
| `<length>` | End date of preceding task | Start date + `length` | n/a |
| `until <otherTaskId>` | End date of preceding task | Start date of previously specified task `otherTaskID` | n/a |
> **Note**
> Support for keyword `until` was added in (v10.9.0+). This can be used to define a task which is running until some other specific task or milestone starts.
For simplicity, the table does not show the use of multiple tasks listed with the `after` keyword. Here is an example of how to use it and how it's interpreted:
```mermaid-example
gantt
apple :a, 2017-07-20, 1w
banana :crit, b, 2017-07-23, 1d
cherry :active, c, after b a, 1d
kiwi :d, 2017-07-20, until b c
```
```mermaid
gantt
apple :a, 2017-07-20, 1w
banana :crit, b, 2017-07-23, 1d
cherry :active, c, after b a, 1d
kiwi :d, 2017-07-20, until b c
```
### Title
The `title` is an _optional_ string to be displayed at the top of the Gantt chart to describe the chart as a whole.
### Excludes
The `excludes` is an _optional_ attribute that accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".
These date will be marked on the graph, and be excluded from the duration calculation of tasks. Meaning that if there are excluded dates during a task interval, the number of 'skipped' days will be added to the end of the task to ensure the duration is as specified in the code.
#### Weekend (v\11.0.0+)
When excluding weekends, it is possible to configure the weekends to be either Friday and Saturday or Saturday and Sunday. By default weekends are Saturday and Sunday.
To define the weekend start day, there is an _optional_ attribute `weekend` that can be added in a new line followed by either `friday` or `saturday`.
```mermaid-example
gantt
title A Gantt Diagram Excluding Fri - Sat weekends
dateFormat YYYY-MM-DD
excludes weekends
weekend friday
section Section
A task :a1, 2024-01-01, 30d
Another task :after a1, 20d
```
```mermaid
gantt
title A Gantt Diagram Excluding Fri - Sat weekends
dateFormat YYYY-MM-DD
excludes weekends
weekend friday
section Section
A task :a1, 2024-01-01, 30d
Another task :after a1, 20d
```
### Section statements
You can divide the chart into various sections, for example to separate different parts of a project like development and documentation.
To do so, start a line with the `section` keyword and give it a name. (Note that unlike with the [title for the entire chart](#title), this name is _required_.
### Milestones
You can add milestones to the diagrams. Milestones differ from tasks as they represent a single instant in time and are identified by the keyword `milestone`. Below is an example on how to use milestones. As you may notice, the exact location of the milestone is determined by the initial date for the milestone and the "duration" of the task this way: _initial date_+_duration_/2.
```mermaid-example
gantt
dateFormat HH:mm
axisFormat %H:%M
Initial milestone : milestone, m1, 17:49, 2m
Task A : 10m
Task B : 5m
Final milestone : milestone, m2, 18:08, 4m
```
```mermaid
gantt
dateFormat HH:mm
axisFormat %H:%M
Initial milestone : milestone, m1, 17:49, 2m
Task A : 10m
Task B : 5m
Final milestone : milestone, m2, 18:08, 4m
```
### Vertical Markers
The `vert` keyword lets you add vertical lines to your Gantt chart, making it easy to highlight important dates like deadlines, events, or checkpoints. These markers extend across the entire chart and are positioned based on the date you provide. Unlike milestones, vertical markers dont take up a row. Theyre purely visual reference points that help break up the timeline and make important moments easier to spot.
```mermaid-example
gantt
dateFormat HH:mm
axisFormat %H:%M
Initial vert : vert, v1, 17:30, 2m
Task A : 3m
Task B : 8m
Final vert : vert, v2, 17:58, 4m
```
```mermaid
gantt
dateFormat HH:mm
axisFormat %H:%M
Initial vert : vert, v1, 17:30, 2m
Task A : 3m
Task B : 8m
Final vert : vert, v2, 17:58, 4m
```
## Setting dates
`dateFormat` defines the format of the date **input** of your gantt elements. How these dates are represented in the rendered chart **output** are defined by `axisFormat`.
### Input date format
The default input date format is `YYYY-MM-DD`. You can define your custom `dateFormat`.
```markdown
dateFormat YYYY-MM-DD
```
The following formatting options are supported:
| Input | Example | Description |
| ---------- | -------------- | ------------------------------------------------------ |
| `YYYY` | 2014 | 4 digit year |
| `YY` | 14 | 2 digit year |
| `Q` | 1..4 | Quarter of year. Sets month to first month in quarter. |
| `M MM` | 1..12 | Month number |
| `MMM MMMM` | January..Dec | Month name in locale set by `dayjs.locale()` |
| `D DD` | 1..31 | Day of month |
| `Do` | 1st..31st | Day of month with ordinal |
| `DDD DDDD` | 1..365 | Day of year |
| `X` | 1410715640.579 | Unix timestamp |
| `x` | 1410715640579 | Unix ms timestamp |
| `H HH` | 0..23 | 24 hour time |
| `h hh` | 1..12 | 12 hour time used with `a A`. |
| `a A` | am pm | Post or ante meridiem |
| `m mm` | 0..59 | Minutes |
| `s ss` | 0..59 | Seconds |
| `S` | 0..9 | Tenths of a second |
| `SS` | 0..99 | Hundreds of a second |
| `SSS` | 0..999 | Thousandths of a second |
| `Z ZZ` | +12:00 | Offset from UTC as +-HH:mm, +-HHmm, or Z |
More info in: <https://day.js.org/docs/en/parse/string-format/>
### Output date format on the axis
The default output date format is `YYYY-MM-DD`. You can define your custom `axisFormat`, like `2020-Q1` for the first quarter of the year 2020.
```markdown
axisFormat %Y-%m-%d
```
The following formatting strings are supported:
| Format | Definition |
| ------ | ------------------------------------------------------------------------------------------ |
| %a | abbreviated weekday name |
| %A | full weekday name |
| %b | abbreviated month name |
| %B | full month name |
| %c | date and time, as "%a %b %e %H:%M:%S %Y" |
| %d | zero-padded day of the month as a decimal number \[01,31] |
| %e | space-padded day of the month as a decimal number \[ 1,31]; equivalent to %\_d |
| %H | hour (24-hour clock) as a decimal number \[00,23] |
| %I | hour (12-hour clock) as a decimal number \[01,12] |
| %j | day of the year as a decimal number \[001,366] |
| %m | month as a decimal number \[01,12] |
| %M | minute as a decimal number \[00,59] |
| %L | milliseconds as a decimal number \[000, 999] |
| %p | either AM or PM |
| %S | second as a decimal number \[00,61] |
| %U | week number of the year (Sunday as the first day of the week) as a decimal number \[00,53] |
| %w | weekday as a decimal number \[0(Sunday),6] |
| %W | week number of the year (Monday as the first day of the week) as a decimal number \[00,53] |
| %x | date, as "%m/%d/%Y" |
| %X | time, as "%H:%M:%S" |
| %y | year without century as a decimal number \[00,99] |
| %Y | year with century as a decimal number |
| %Z | time zone offset, such as "-0700" |
| %% | a literal "%" character |
More info in: <https://github.com/d3/d3-time-format/tree/v4.0.0#locale_format>
### Axis ticks (v10.3.0+)
The default output ticks are auto. You can custom your `tickInterval`, like `1day` or `1week`.
```markdown
tickInterval 1day
```
The pattern is:
```javascript
/^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/;
```
More info in: <https://github.com/d3/d3-time#interval_every>
Week-based `tickInterval`s start the week on sunday by default. If you wish to specify another weekday on which the `tickInterval` should start, use the `weekday` option:
```mermaid-example
gantt
tickInterval 1week
weekday monday
```
```mermaid
gantt
tickInterval 1week
weekday monday
```
> **Warning**
> `millisecond` and `second` support was added in v10.3.0
## Output in compact mode
The compact mode allows you to display multiple tasks in the same row. Compact mode can be enabled for a gantt chart by setting the display mode of the graph via preceding YAML settings.
```mermaid-example
---
displayMode: compact
---
gantt
title A Gantt Diagram
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :a2, 2014-01-20, 25d
Another one :a3, 2014-02-10, 20d
```
```mermaid
---
displayMode: compact
---
gantt
title A Gantt Diagram
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :a2, 2014-01-20, 25d
Another one :a3, 2014-02-10, 20d
```
## Comments
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax.
```mermaid-example
gantt
title A Gantt Diagram
%% This is a comment
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :after a1, 20d
section Another
Task in Another :2014-01-12, 12d
another task :24d
```
```mermaid
gantt
title A Gantt Diagram
%% This is a comment
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :after a1, 20d
section Another
Task in Another :2014-01-12, 12d
another task :24d
```
## Styling
Styling of the Gantt diagram is done by defining a number of CSS classes. During rendering, these classes are extracted from the file located at src/diagrams/gantt/styles.js
### Classes used
| Class | Description |
| --------------------- | ---------------------------------------------------------------------- |
| grid.tick | Styling for the Grid Lines |
| grid.path | Styling for the Grid's borders |
| .taskText | Task Text Styling |
| .taskTextOutsideRight | Styling for Task Text that exceeds the activity bar towards the right. |
| .taskTextOutsideLeft | Styling for Task Text that exceeds the activity bar, towards the left. |
| todayMarker | Toggle and Styling for the "Today Marker" |
### Sample stylesheet
```css
.grid .tick {
stroke: lightgrey;
opacity: 0.3;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
#tag {
color: white;
background: #fa283d;
width: 150px;
position: absolute;
display: none;
padding: 3px 6px;
margin-left: -80px;
font-size: 11px;
}
#tag:before {
border: solid transparent;
content: ' ';
height: 0;
left: 50%;
margin-left: -5px;
position: absolute;
width: 0;
border-width: 10px;
border-bottom-color: #fa283d;
top: -20px;
}
.taskText {
fill: white;
text-anchor: middle;
}
.taskTextOutsideRight {
fill: black;
text-anchor: start;
}
.taskTextOutsideLeft {
fill: black;
text-anchor: end;
}
```
## Today marker
You can style or hide the marker for the current date. To style it, add a value for the `todayMarker` key.
```
todayMarker stroke-width:5px,stroke:#0f0,opacity:0.5
```
To hide the marker, set `todayMarker` to `off`.
```
todayMarker off
```
## Configuration
It is possible to adjust the margins for rendering the gantt diagram.
This is done by defining the `ganttConfig` part of the configuration object.
How to use the CLI is described in the [mermaidCLI](../config/mermaidCLI.md) page.
mermaid.ganttConfig can be set to a JSON string with config parameters or the corresponding object.
```javascript
mermaid.ganttConfig = {
titleTopMargin: 25, // Margin top for the text over the diagram
barHeight: 20, // The height of the bars in the graph
barGap: 4, // The margin between the different activities in the gantt diagram
topPadding: 75, // Margin between title and gantt diagram and between axis and gantt diagram.
rightPadding: 75, // The space allocated for the section name to the right of the activities
leftPadding: 75, // The space allocated for the section name to the left of the activities
gridLineStartPadding: 10, // Vertical starting position of the grid lines
fontSize: 12, // Font size
sectionFontSize: 24, // Font size for sections
numberSectionStyles: 1, // The number of alternating section styles
axisFormat: '%d/%m', // Date/time format of the axis
tickInterval: '1week', // Axis ticks
topAxis: true, // When this flag is set, date labels will be added to the top of the chart
displayMode: 'compact', // Turns compact mode on
weekday: 'sunday', // On which day a week-based interval should start
};
```
### Possible configuration params:
| Param | Description | Default value |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------- |
| mirrorActor | Turns on/off the rendering of actors below the diagram as well as above it | false |
| bottomMarginAdj | Adjusts how far down the graph ended. Wide borders styles with css could generate unwanted clipping which is why this config param exists. | 1 |
## Interaction
It is possible to bind a click event to a task. The click can lead to either a javascript callback or to a link which will be opened in the current browser tab. **Note**: This functionality is disabled when using `securityLevel='strict'` and enabled when using `securityLevel='loose'`.
```
click taskId call callback(arguments)
click taskId href URL
```
- taskId is the id of the task
- callback is the name of a javascript function defined on the page displaying the graph, the function will be called with the taskId as the parameter if no other arguments are specified.
Beginner's tip—a full example using interactive links in an HTML context:
```html
<body>
<pre class="mermaid">
gantt
dateFormat YYYY-MM-DD
section Clickable
Visit mermaidjs :active, cl1, 2014-01-07, 3d
Print arguments :cl2, after cl1, 3d
Print task :cl3, after cl2, 3d
click cl1 href "https://mermaidjs.github.io/"
click cl2 call printArguments("test1", "test2", test3)
click cl3 call printTask()
</pre>
<script>
const printArguments = function (arg1, arg2, arg3) {
alert('printArguments called with arguments: ' + arg1 + ', ' + arg2 + ', ' + arg3);
};
const printTask = function (taskId) {
alert('taskId: ' + taskId);
};
const config = {
startOnLoad: true,
securityLevel: 'loose',
};
mermaid.initialize(config);
</script>
</body>
```
## Examples
### Bar chart (using gantt chart)
```mermaid-example
gantt
title Git Issues - days since last update
dateFormat X
axisFormat %s
section Issue19062
71 : 0, 71
section Issue19401
36 : 0, 36
section Issue193
34 : 0, 34
section Issue7441
9 : 0, 9
section Issue1300
5 : 0, 5
```
```mermaid
gantt
title Git Issues - days since last update
dateFormat X
axisFormat %s
section Issue19062
71 : 0, 71
section Issue19401
36 : 0, 36
section Issue193
34 : 0, 34
section Issue7441
9 : 0, 9
section Issue1300
5 : 0, 5
```
### Timeline (with comments, CSS, config in frontmatter)
```mermaid-example
---
# Frontmatter config, YAML comments
title: Ignored if specified in chart
displayMode: compact #gantt specific setting but works at this level too
config:
# theme: forest
# themeCSS: " #item36 { fill: CadetBlue } "
themeCSS: " // YAML supports multiline strings using a newline markers: \n
#item36 { fill: CadetBlue } \n
// Custom marker workaround CSS from forum (below) \n
rect[id^=workaround] { height: calc(100% - 50px) ; transform: translate(9px, 25px); y: 0; width: 1.5px; stroke: none; fill: red; } \n
text[id^=workaround] { fill: red; y: 100%; font-size: 15px;}
"
gantt:
useWidth: 400
rightPadding: 0
topAxis: true #false
numberSectionStyles: 2
---
gantt
title Timeline - Gantt Sampler
dateFormat YYYY
axisFormat %y
%% this next line doesn't recognise 'decade' or 'year', but will silently ignore
tickInterval 1decade
section Issue19062
71 : item71, 1900, 1930
section Issue19401
36 : item36, 1913, 1935
section Issue1300
94 : item94, 1910, 1915
5 : item5, 1920, 1925
0 : milestone, item0, 1918, 1s
9 : vert, 1906, 1s %% not yet official
64 : workaround, 1923, 1s %% custom CSS object https://github.com/mermaid-js/mermaid/issues/3250
```
```mermaid
---
# Frontmatter config, YAML comments
title: Ignored if specified in chart
displayMode: compact #gantt specific setting but works at this level too
config:
# theme: forest
# themeCSS: " #item36 { fill: CadetBlue } "
themeCSS: " // YAML supports multiline strings using a newline markers: \n
#item36 { fill: CadetBlue } \n
// Custom marker workaround CSS from forum (below) \n
rect[id^=workaround] { height: calc(100% - 50px) ; transform: translate(9px, 25px); y: 0; width: 1.5px; stroke: none; fill: red; } \n
text[id^=workaround] { fill: red; y: 100%; font-size: 15px;}
"
gantt:
useWidth: 400
rightPadding: 0
topAxis: true #false
numberSectionStyles: 2
---
gantt
title Timeline - Gantt Sampler
dateFormat YYYY
axisFormat %y
%% this next line doesn't recognise 'decade' or 'year', but will silently ignore
tickInterval 1decade
section Issue19062
71 : item71, 1900, 1930
section Issue19401
36 : item36, 1913, 1935
section Issue1300
94 : item94, 1910, 1915
5 : item5, 1920, 1925
0 : milestone, item0, 1918, 1s
9 : vert, 1906, 1s %% not yet official
64 : workaround, 1923, 1s %% custom CSS object https://github.com/mermaid-js/mermaid/issues/3250
```
<!--- cspell:ignore isadded --->
File diff suppressed because it is too large Load Diff
+161
View File
@@ -0,0 +1,161 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/kanban.md](../../packages/mermaid/src/docs/syntax/kanban.md).
# Mermaid Kanban Diagram Documentation
Mermaids Kanban diagram allows you to create visual representations of tasks moving through different stages of a workflow. This guide explains how to use the Kanban diagram syntax, based on the provided example.
## Overview
A Kanban diagram in Mermaid starts with the kanban keyword, followed by the definition of columns (stages) and tasks within those columns.
```mermaid-example
kanban
column1[Column Title]
task1[Task Description]
```
```mermaid
kanban
column1[Column Title]
task1[Task Description]
```
## Defining Columns
Columns represent the different stages in your workflow, such as “Todo,” “In Progress,” “Done,” etc. Each column is defined using a unique identifier and a title enclosed in square brackets.
**Syntax:**
```
columnId[Column Title]
```
- columnId: A unique identifier for the column.
- \[Column Title]: The title displayed on the column header.
Like this `id1[Todo]`
## Adding Tasks to Columns
Tasks are listed under their respective columns with an indentation. Each task also has a unique identifier and a description enclosed in square brackets.
**Syntax:**
```
taskId[Task Description]
```
```
• taskId: A unique identifier for the task.
• [Task Description]: The description of the task.
```
**Example:**
```
docs[Create Documentation]
```
## Adding Metadata to Tasks
You can include additional metadata for each task using the @{ ... } syntax. Metadata can contain key-value pairs like assigned, ticket, priority, etc. This will be rendered added to the rendering of the node.
## Supported Metadata Keys
```
• assigned: Specifies who is responsible for the task.
• ticket: Links the task to a ticket or issue number.
• priority: Indicates the urgency of the task. Allowed values: 'Very High', 'High', 'Low' and 'Very Low'
```
```mermaid-example
kanban
todo[Todo]
id3[Update Database Function]@{ ticket: MC-2037, assigned: 'knsv', priority: 'High' }
```
```mermaid
kanban
todo[Todo]
id3[Update Database Function]@{ ticket: MC-2037, assigned: 'knsv', priority: 'High' }
```
## Configuration Options
You can customize the Kanban diagram using a configuration block at the beginning of your markdown file. This is useful for setting global settings like a base URL for tickets. Currently there is one configuration option for kanban diagrams `ticketBaseUrl`. This can be set as in the following example:
```yaml
---
config:
kanban:
ticketBaseUrl: 'https://yourproject.atlassian.net/browse/#TICKET#'
---
```
When the kanban item has an assigned ticket number the ticket number in the diagram will have a link to an external system where the ticket is defined. The `ticketBaseUrl` sets the base URL to the external system and #TICKET# is replaced with the ticket value from task metadata to create a valid link.
## Full Example
Below is the full Kanban diagram based on the provided example:
```mermaid-example
---
config:
kanban:
ticketBaseUrl: 'https://mermaidchart.atlassian.net/browse/#TICKET#'
---
kanban
Todo
[Create Documentation]
docs[Create Blog about the new diagram]
[In progress]
id6[Create renderer so that it works in all cases. We also add some extra text here for testing purposes. And some more just for the extra flare.]
id9[Ready for deploy]
id8[Design grammar]@{ assigned: 'knsv' }
id10[Ready for test]
id4[Create parsing tests]@{ ticket: MC-2038, assigned: 'K.Sveidqvist', priority: 'High' }
id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
id11[Done]
id5[define getData]
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: MC-2036, priority: 'Very High'}
id3[Update DB function]@{ ticket: MC-2037, assigned: knsv, priority: 'High' }
id12[Can't reproduce]
id3[Weird flickering in Firefox]
```
```mermaid
---
config:
kanban:
ticketBaseUrl: 'https://mermaidchart.atlassian.net/browse/#TICKET#'
---
kanban
Todo
[Create Documentation]
docs[Create Blog about the new diagram]
[In progress]
id6[Create renderer so that it works in all cases. We also add some extra text here for testing purposes. And some more just for the extra flare.]
id9[Ready for deploy]
id8[Design grammar]@{ assigned: 'knsv' }
id10[Ready for test]
id4[Create parsing tests]@{ ticket: MC-2038, assigned: 'K.Sveidqvist', priority: 'High' }
id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
id11[Done]
id5[define getData]
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: MC-2036, priority: 'Very High'}
id3[Update DB function]@{ ticket: MC-2037, assigned: knsv, priority: 'High' }
id12[Can't reproduce]
id3[Weird flickering in Firefox]
```
In conclusion, creating a Kanban diagram in Mermaid is a straightforward process that effectively visualizes your workflow. Start by using the kanban keyword to initiate the diagram. Define your columns with unique identifiers and titles to represent different stages of your project. Under each column, list your tasks—also with unique identifiers—and provide detailed descriptions as needed. Remember that proper indentation is crucial; tasks must be indented under their parent columns to maintain the correct structure.
You can enhance your diagram by adding optional metadata to tasks using the @{ ... } syntax, which allows you to include additional context such as assignee, ticket numbers, and priority levels. For further customization, utilize the configuration block at the top of your file to set global options like ticketBaseUrl for linking tickets directly from your diagram.
By adhering to these guidelines—ensuring unique identifiers, proper indentation, and utilizing metadata and configuration options—you can create a comprehensive and customized Kanban board that effectively maps out your projects workflow using Mermaid.
@@ -0,0 +1,335 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/mindmap.md](../../packages/mermaid/src/docs/syntax/mindmap.md).
# Mindmap
> Mindmap: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stable except for the icon integration which is the experimental part.
"A mind map is a diagram used to visually organize information into a hierarchy, showing relationships among pieces of the whole. It is often created around a single concept, drawn as an image in the center of a blank page, to which associated representations of ideas such as images, words and parts of words are added. Major ideas are connected directly to the central concept, and other ideas branch out from those major ideas." Wikipedia
### An example of a mindmap.
```mermaid-example
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness<br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
Pen and paper
Mermaid
```
```mermaid
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness<br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
Pen and paper
Mermaid
```
## Syntax
The syntax for creating Mindmaps is simple and relies on indentation for setting the levels in the hierarchy.
In the following example you can see how there are 3 different levels. One with starting at the left of the text and another level with two rows starting at the same column, defining the node A. At the end there is one more level where the text is indented further than the previous lines defining the nodes B and C.
```
mindmap
Root
A
B
C
```
In summary is a simple text outline where there is one node at the root level called `Root` which has one child `A`. `A` in turn has two children `B`and `C`. In the diagram below we can see this rendered as a mindmap.
```mermaid-example
mindmap
Root
A
B
C
```
```mermaid
mindmap
Root
A
B
C
```
In this way we can use a text outline to generate a hierarchical mindmap.
## Different shapes
Mermaid mindmaps can show nodes using different shapes. When specifying a shape for a node the syntax is similar to flowchart nodes, with an id followed by the shape definition and with the text within the shape delimiters. Where possible we try/will try to keep the same shapes as for flowcharts, even though they are not all supported from the start.
Mindmap can show the following shapes:
### Square
```mermaid-example
mindmap
id[I am a square]
```
```mermaid
mindmap
id[I am a square]
```
### Rounded square
```mermaid-example
mindmap
id(I am a rounded square)
```
```mermaid
mindmap
id(I am a rounded square)
```
### Circle
```mermaid-example
mindmap
id((I am a circle))
```
```mermaid
mindmap
id((I am a circle))
```
### Bang
```mermaid-example
mindmap
id))I am a bang((
```
```mermaid
mindmap
id))I am a bang((
```
### Cloud
```mermaid-example
mindmap
id)I am a cloud(
```
```mermaid
mindmap
id)I am a cloud(
```
### Hexagon
```mermaid-example
mindmap
id{{I am a hexagon}}
```
```mermaid
mindmap
id{{I am a hexagon}}
```
### Default
```mermaid-example
mindmap
I am the default shape
```
```mermaid
mindmap
I am the default shape
```
More shapes will be added, beginning with the shapes available in flowcharts.
# Icons and classes
## Icons
As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and [Font Awesome 5](https://fontawesome.com/v5/search?o=r&m=free) are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
```mermaid-example
mindmap
Root
A
::icon(fa fa-book)
B(B)
::icon(mdi mdi-skull-outline)
```
```mermaid
mindmap
Root
A
::icon(fa fa-book)
B(B)
::icon(mdi mdi-skull-outline)
```
## Classes
Again the syntax for adding classes is similar to flowcharts. You can add classes using a triple colon following a number of css classes separated by space. In the following example one of the nodes has two custom classes attached urgent turning the background red and the text white and large increasing the font size:
```mermaid-example
mindmap
Root
A[A]
:::urgent large
B(B)
C
```
```mermaid
mindmap
Root
A[A]
:::urgent large
B(B)
C
```
_These classes need to be supplied by the site administrator._
## Unclear indentation
The actual indentation does not really matter only compared with the previous rows. If we take the previous example and disrupt it a little we can see how the calculations are performed. Let us start with placing C with a smaller indentation than `B` but larger then `A`.
```
mindmap
Root
A
B
C
```
This outline is unclear as `B` clearly is a child of `A` but when we move on to `C` the clarity is lost. `C` is neither a child of `B` with a higher indentation nor does it have the same indentation as `B`. The only thing that is clear is that the first node with smaller indentation, indicating a parent, is A. Then Mermaid relies on this known truth and compensates for the unclear indentation and selects `A` as a parent of `C` leading till the same diagram with `B` and `C` as siblings.
```mermaid-example
mindmap
Root
A
B
C
```
```mermaid
mindmap
Root
A
B
C
```
## Markdown Strings
The "Markdown Strings" feature enhances mind maps by offering a more versatile string type, which supports text formatting options such as bold and italics, and automatically wraps text within labels.
```mermaid-example
mindmap
id1["`**Root** with
a second line
Unicode works too: 🤓`"]
id2["`The dog in **the** hog... a *very long text* that wraps to a new line`"]
id3[Regular labels still works]
```
```mermaid
mindmap
id1["`**Root** with
a second line
Unicode works too: 🤓`"]
id2["`The dog in **the** hog... a *very long text* that wraps to a new line`"]
id3[Regular labels still works]
```
Formatting:
- For bold text, use double asterisks \*\* before and after the text.
- For italics, use single asterisks \* before and after the text.
- With traditional strings, you needed to add <br> tags for text to wrap in nodes. However, markdown strings automatically wrap text when it becomes too long and allows you to start a new line by simply using a newline character instead of a <br> tag.
## Integrating with your library/website.
Mindmap uses the experimental lazy loading & async rendering features which could change in the future. From version 9.4.0 this diagram is included in mermaid but use lazy loading in order to keep the size of mermaid down. This is important in order to be able to add additional diagrams going forward.
You can still use the pre 9.4.0 method to add mermaid with mindmaps to a web page:
```html
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9.3.0/dist/mermaid.esm.min.mjs';
import mindmap from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-mindmap@9.3.0/dist/mermaid-mindmap.esm.min.mjs';
await mermaid.registerExternalDiagrams([mindmap]);
</script>
```
From version 9.4.0 you can simplify this code to:
```html
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
</script>
```
You can also refer the [implementation in the live editor](https://github.com/mermaid-js/mermaid-live-editor/blob/develop/src/lib/util/mermaid.ts) to see how the async loading is done.
<!---
cspell:locale en,en-gb
cspell:ignore Buzan
--->
## Layouts
Mermaid also supports a Tidy Tree layout for mindmaps.
```
---
config:
layout: tidy-tree
---
mindmap
root((mindmap is a long thing))
A
B
C
D
```
Instructions to add and register tidy-tree layout are present in [Tidy Tree Configuration](/config/tidy-tree)
+153
View File
@@ -0,0 +1,153 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/packet.md](../../packages/mermaid/src/docs/syntax/packet.md).
# Packet Diagram (v11.0.0+)
## Introduction
A packet diagram is a visual representation used to illustrate the structure and contents of a network packet. Network packets are the fundamental units of data transferred over a network.
## Usage
This diagram type is particularly useful for developers, network engineers, educators, and students who require a clear and concise way to represent the structure of network packets.
## Syntax
```
packet
start: "Block name" %% Single-bit block
start-end: "Block name" %% Multi-bit blocks
... More Fields ...
```
### Bits Syntax (v11.7.0+)
Using start and end bit counts can be difficult, especially when modifying a design. For this we add a bit count field, which starts from the end of the previous field automagically. Use `+<count>` to set the number of bits, thus:
```
packet
+1: "Block name" %% Single-bit block
+8: "Block name" %% 8-bit block
9-15: "Manually set start and end, it's fine to mix and match"
... More Fields ...
```
## Examples
```mermaid-example
---
title: "TCP Packet"
---
packet
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
64-95: "Acknowledgment Number"
96-99: "Data Offset"
100-105: "Reserved"
106: "URG"
107: "ACK"
108: "PSH"
109: "RST"
110: "SYN"
111: "FIN"
112-127: "Window"
128-143: "Checksum"
144-159: "Urgent Pointer"
160-191: "(Options and Padding)"
192-255: "Data (variable length)"
```
```mermaid
---
title: "TCP Packet"
---
packet
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
64-95: "Acknowledgment Number"
96-99: "Data Offset"
100-105: "Reserved"
106: "URG"
107: "ACK"
108: "PSH"
109: "RST"
110: "SYN"
111: "FIN"
112-127: "Window"
128-143: "Checksum"
144-159: "Urgent Pointer"
160-191: "(Options and Padding)"
192-255: "Data (variable length)"
```
```mermaid-example
packet
title UDP Packet
+16: "Source Port"
+16: "Destination Port"
32-47: "Length"
48-63: "Checksum"
64-95: "Data (variable length)"
```
```mermaid
packet
title UDP Packet
+16: "Source Port"
+16: "Destination Port"
32-47: "Length"
48-63: "Checksum"
64-95: "Data (variable length)"
```
## Details of Syntax
- **Ranges**: Each line after the title represents a different field in the packet. The range (e.g., `0-15`) indicates the bit positions in the packet.
- **Field Description**: A brief description of what the field represents, enclosed in quotes.
## Configuration
Please refer to the [configuration](/config/schema-docs/config-defs-packet-diagram-config.html) guide for details.
<!--
Theme variables are not currently working due to a mermaid bug. The passed values are not being propagated into styles function.
## Theme Variables
| Property | Description | Default Value |
| ---------------- | -------------------------- | ------------- |
| byteFontSize | Font size of the bytes | '10px' |
| startByteColor | Color of the starting byte | 'black' |
| endByteColor | Color of the ending byte | 'black' |
| labelColor | Color of the labels | 'black' |
| labelFontSize | Font size of the labels | '12px' |
| titleColor | Color of the title | 'black' |
| titleFontSize | Font size of the title | '14px' |
| blockStrokeColor | Color of the block stroke | 'black' |
| blockStrokeWidth | Width of the block stroke | '1' |
| blockFillColor | Fill color of the block | '#efefef' |
## Example on config and theme
```mermaid-example
---
config:
packet:
showBits: true
themeVariables:
packet:
startByteColor: red
---
packet
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
```
-->
+93
View File
@@ -0,0 +1,93 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/pie.md](../../packages/mermaid/src/docs/syntax/pie.md).
# Pie chart diagrams
> A pie chart (or a circle chart) is a circular statistical graphic, which is divided into slices to illustrate numerical proportion. In a pie chart, the arc length of each slice (and consequently its central angle and area), is proportional to the quantity it represents. While it is named for its resemblance to a pie which has been sliced, there are variations on the way it can be presented. The earliest known pie chart is generally credited to William Playfair's Statistical Breviary of 1801
> -Wikipedia
Mermaid can render Pie Chart diagrams.
```mermaid-example
pie title Pets adopted by volunteers
"Dogs" : 386
"Cats" : 85
"Rats" : 15
```
```mermaid
pie title Pets adopted by volunteers
"Dogs" : 386
"Cats" : 85
"Rats" : 15
```
## Syntax
Drawing a pie chart is really simple in mermaid.
- Start with `pie` keyword to begin the diagram
- `showData` to render the actual data values after the legend text. This is **_OPTIONAL_**
- Followed by `title` keyword and its value in string to give a title to the pie-chart. This is **_OPTIONAL_**
- Followed by dataSet. Pie slices will be ordered clockwise in the same order as the labels.
- `label` for a section in the pie diagram within `" "` quotes.
- Followed by `:` colon as separator
- Followed by `positive numeric value` (supported up to two decimal places)
**Note:**
> Pie chart values must be **positive numbers greater than zero**.
> **Negative values are not allowed** and will result in an error.
\[pie] \[showData] (OPTIONAL)
\[title] \[titlevalue] (OPTIONAL)
"\[datakey1]" : \[dataValue1]
"\[datakey2]" : \[dataValue2]
"\[datakey3]" : \[dataValue3]
.
.
## Example
```mermaid-example
---
config:
pie:
textPosition: 0.5
themeVariables:
pieOuterStrokeWidth: "5px"
---
pie showData
title Key elements in Product X
"Calcium" : 42.96
"Potassium" : 50.05
"Magnesium" : 10.01
"Iron" : 5
```
```mermaid
---
config:
pie:
textPosition: 0.5
themeVariables:
pieOuterStrokeWidth: "5px"
---
pie showData
title Key elements in Product X
"Calcium" : 42.96
"Potassium" : 50.05
"Magnesium" : 10.01
"Iron" : 5
```
## Configuration
Possible pie diagram configuration parameters:
| Parameter | Description | Default value |
| -------------- | ------------------------------------------------------------------------------------------------------------ | ------------- |
| `textPosition` | The axial position of the pie slice labels, from 0.0 at the center to 1.0 at the outside edge of the circle. | `0.75` |
@@ -0,0 +1,267 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/quadrantChart.md](../../packages/mermaid/src/docs/syntax/quadrantChart.md).
# Quadrant Chart
> A quadrant chart is a visual representation of data that is divided into four quadrants. It is used to plot data points on a two-dimensional grid, with one variable represented on the x-axis and another variable represented on the y-axis. The quadrants are determined by dividing the chart into four equal parts based on a set of criteria that is specific to the data being analyzed. Quadrant charts are often used to identify patterns and trends in data, and to prioritize actions based on the position of data points within the chart. They are commonly used in business, marketing, and risk management, among other fields.
## Example
```mermaid-example
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```
```mermaid
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```
## Syntax
> **Note**
> If there are no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
> If there are points **x-axis** labels will rendered from the left of the respective quadrant also they will be displayed at the bottom of the chart, and **y-axis** labels will be rendered at the bottom of the respective quadrant, the quadrant text will render at the top of the respective quadrant.
> **Note**
> For points x and y value min value is 0 and max value is 1.
### Title
The title is a short description of the chart and it will always render on top of the chart.
#### Example
```
quadrantChart
title This is a sample example
```
### x-axis
The x-axis determines what text would be displayed in the x-axis. In x-axis there is two part **left** and **right** you can pass **both** or you can pass only **left**. The statement should start with `x-axis` then the `left axis text` followed by the delimiter `-->` then `right axis text`.
#### Example
1. `x-axis <text> --> <text>` both the left and right axis text will be rendered.
2. `x-axis <text>` only the left axis text will be rendered.
### y-axis
The y-axis determines what text would be displayed in the y-axis. In y-axis there is two part **top** and **bottom** you can pass **both** or you can pass only **bottom**. The statement should start with `y-axis` then the `bottom axis text` followed by the delimiter `-->` then `top axis text`.
#### Example
1. `y-axis <text> --> <text>` both the bottom and top axis text will be rendered.
2. `y-axis <text>` only the bottom axis text will be rendered.
### Quadrants text
The `quadrant-[1,2,3,4]` determine what text would be displayed inside the quadrants.
#### Example
1. `quadrant-1 <text>` determine what text will be rendered inside the top right quadrant.
2. `quadrant-2 <text>` determine what text will be rendered inside the top left quadrant.
3. `quadrant-3 <text>` determine what text will be rendered inside the bottom left quadrant.
4. `quadrant-4 <text>` determine what text will be rendered inside the bottom right quadrant.
### Points
Points are used to plot a circle inside the quadrantChart. The syntax is `<text>: [x, y]` here x and y value is in the range 0 - 1.
#### Example
1. `Point 1: [0.75, 0.80]` here the Point 1 will be drawn in the top right quadrant.
2. `Point 2: [0.35, 0.24]` here the Point 2 will be drawn in the bottom left quadrant.
## Chart Configurations
| Parameter | Description | Default value |
| --------------------------------- | -------------------------------------------------------------------------------------------------- | :-----------: |
| chartWidth | Width of the chart | 500 |
| chartHeight | Height of the chart | 500 |
| titlePadding | Top and Bottom padding of the title | 10 |
| titleFontSize | Title font size | 20 |
| quadrantPadding | Padding outside all the quadrants | 5 |
| quadrantTextTopPadding | Quadrant text top padding when text is drawn on top ( not data points are there) | 5 |
| quadrantLabelFontSize | Quadrant text font size | 16 |
| quadrantInternalBorderStrokeWidth | Border stroke width inside the quadrants | 1 |
| quadrantExternalBorderStrokeWidth | Quadrant external border stroke width | 2 |
| xAxisLabelPadding | Top and bottom padding of x-axis text | 5 |
| xAxisLabelFontSize | X-axis texts font size | 16 |
| xAxisPosition | Position of x-axis (top , bottom) if there are points the x-axis will always be rendered in bottom | 'top' |
| yAxisLabelPadding | Left and Right padding of y-axis text | 5 |
| yAxisLabelFontSize | Y-axis texts font size | 16 |
| yAxisPosition | Position of y-axis (left , right) | 'left' |
| pointTextPadding | Padding between point and the below text | 5 |
| pointLabelFontSize | Point text font size | 12 |
| pointRadius | Radius of the point to be drawn | 5 |
## Chart Theme Variables
| Parameter | Description |
| -------------------------------- | --------------------------------------- |
| quadrant1Fill | Fill color of the top right quadrant |
| quadrant2Fill | Fill color of the top left quadrant |
| quadrant3Fill | Fill color of the bottom left quadrant |
| quadrant4Fill | Fill color of the bottom right quadrant |
| quadrant1TextFill | Text color of the top right quadrant |
| quadrant2TextFill | Text color of the top left quadrant |
| quadrant3TextFill | Text color of the bottom left quadrant |
| quadrant4TextFill | Text color of the bottom right quadrant |
| quadrantPointFill | Points fill color |
| quadrantPointTextFill | Points text color |
| quadrantXAxisTextFill | X-axis text color |
| quadrantYAxisTextFill | Y-axis text color |
| quadrantInternalBorderStrokeFill | Quadrants inner border color |
| quadrantExternalBorderStrokeFill | Quadrants outer border color |
| quadrantTitleFill | Title color |
## Example on config and theme
```mermaid-example
---
config:
quadrantChart:
chartWidth: 400
chartHeight: 400
themeVariables:
quadrant1TextFill: "ff0000"
---
quadrantChart
x-axis Urgent --> Not Urgent
y-axis Not Important --> "Important ❤"
quadrant-1 Plan
quadrant-2 Do
quadrant-3 Delegate
quadrant-4 Delete
```
```mermaid
---
config:
quadrantChart:
chartWidth: 400
chartHeight: 400
themeVariables:
quadrant1TextFill: "ff0000"
---
quadrantChart
x-axis Urgent --> Not Urgent
y-axis Not Important --> "Important ❤"
quadrant-1 Plan
quadrant-2 Do
quadrant-3 Delegate
quadrant-4 Delete
```
### Point styling
Points can either be styled directly or with defined shared classes
1. Direct styling
```md
Point A: [0.9, 0.0] radius: 12
Point B: [0.8, 0.1] color: #ff3300, radius: 10
Point C: [0.7, 0.2] radius: 25, color: #00ff33, stroke-color: #10f0f0
Point D: [0.6, 0.3] radius: 15, stroke-color: #00ff0f, stroke-width: 5px ,color: #ff33f0
```
2. Classes styling
```md
Point A:::class1: [0.9, 0.0]
Point B:::class2: [0.8, 0.1]
Point C:::class3: [0.7, 0.2]
Point D:::class3: [0.7, 0.2]
classDef class1 color: #109060
classDef class2 color: #908342, radius : 10, stroke-color: #310085, stroke-width: 10px
classDef class3 color: #f00fff, radius : 10
```
#### Available styles:
| Parameter | Description |
| ------------ | ---------------------------------------------------------------------- |
| color | Fill color of the point |
| radius | Radius of the point |
| stroke-width | Border width of the point |
| stroke-color | Border color of the point (useless when stroke-width is not specified) |
> **Note**
> Order of preference:
>
> 1. Direct styles
> 2. Class styles
> 3. Theme styles
## Example on styling
```mermaid-example
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.9, 0.0] radius: 12
Campaign B:::class1: [0.8, 0.1] color: #ff3300, radius: 10
Campaign C: [0.7, 0.2] radius: 25, color: #00ff33, stroke-color: #10f0f0
Campaign D: [0.6, 0.3] radius: 15, stroke-color: #00ff0f, stroke-width: 5px ,color: #ff33f0
Campaign E:::class2: [0.5, 0.4]
Campaign F:::class3: [0.4, 0.5] color: #0000ff
classDef class1 color: #109060
classDef class2 color: #908342, radius : 10, stroke-color: #310085, stroke-width: 10px
classDef class3 color: #f00fff, radius : 10
```
```mermaid
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.9, 0.0] radius: 12
Campaign B:::class1: [0.8, 0.1] color: #ff3300, radius: 10
Campaign C: [0.7, 0.2] radius: 25, color: #00ff33, stroke-color: #10f0f0
Campaign D: [0.6, 0.3] radius: 15, stroke-color: #00ff0f, stroke-width: 5px ,color: #ff33f0
Campaign E:::class2: [0.5, 0.4]
Campaign F:::class3: [0.4, 0.5] color: #0000ff
classDef class1 color: #109060
classDef class2 color: #908342, radius : 10, stroke-color: #310085, stroke-width: 10px
classDef class3 color: #f00fff, radius : 10
```
+269
View File
@@ -0,0 +1,269 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/radar.md](../../packages/mermaid/src/docs/syntax/radar.md).
# Radar Diagram (v11.6.0+)
## Introduction
A radar diagram is a simple way to plot low-dimensional data in a circular format.
It is also known as a **radar chart**, **spider chart**, **star chart**, **cobweb chart**, **polar chart**, or **Kiviat diagram**.
## Usage
This diagram type is particularly useful for developers, data scientists, and engineers who require a clear and concise way to represent data in a circular format.
It is commonly used to graphically summarize and compare the performance of multiple entities across multiple dimensions.
## Syntax
```md
radar-beta
axis A, B, C, D, E
curve c1{1,2,3,4,5}
curve c2{5,4,3,2,1}
... More Fields ...
```
## Examples
```mermaid-example
---
title: "Grades"
---
radar-beta
axis m["Math"], s["Science"], e["English"]
axis h["History"], g["Geography"], a["Art"]
curve a["Alice"]{85, 90, 80, 70, 75, 90}
curve b["Bob"]{70, 75, 85, 80, 90, 85}
max 100
min 0
```
```mermaid
---
title: "Grades"
---
radar-beta
axis m["Math"], s["Science"], e["English"]
axis h["History"], g["Geography"], a["Art"]
curve a["Alice"]{85, 90, 80, 70, 75, 90}
curve b["Bob"]{70, 75, 85, 80, 90, 85}
max 100
min 0
```
```mermaid-example
radar-beta
title Restaurant Comparison
axis food["Food Quality"], service["Service"], price["Price"]
axis ambiance["Ambiance"]
curve a["Restaurant A"]{4, 3, 2, 4}
curve b["Restaurant B"]{3, 4, 3, 3}
curve c["Restaurant C"]{2, 3, 4, 2}
curve d["Restaurant D"]{2, 2, 4, 3}
graticule polygon
max 5
```
```mermaid
radar-beta
title Restaurant Comparison
axis food["Food Quality"], service["Service"], price["Price"]
axis ambiance["Ambiance"]
curve a["Restaurant A"]{4, 3, 2, 4}
curve b["Restaurant B"]{3, 4, 3, 3}
curve c["Restaurant C"]{2, 3, 4, 2}
curve d["Restaurant D"]{2, 2, 4, 3}
graticule polygon
max 5
```
## Details of Syntax
### Title
`title`: The title is an optional field that allows to render a title at the top of the radar diagram.
```
radar-beta
title Title of the Radar Diagram
...
```
### Axis
`axis`: The axis keyword is used to define the axes of the radar diagram.
Each axis is represented by an ID and an optional label.
Multiple axes can be defined in a single line.
```
radar-beta
axis id1["Label1"]
axis id2["Label2"], id3["Label3"]
...
```
### Curve
`curve`: The curve keyword is used to define the data points for a curve in the radar diagram.
Each curve is represented by an ID, an optional label, and a list of values.
Values can be defined by a list of numbers or a list of key-value pairs. If key-value pairs are used, the key represents the axis ID and the value represents the data point. Else, the data points are assumed to be in the order of the axes defined.
Multiple curves can be defined in a single line.
```
radar-beta
axis axis1, axis2, axis3
curve id1["Label1"]{1, 2, 3}
curve id2["Label2"]{4, 5, 6}, id3{7, 8, 9}
curve id4{ axis3: 30, axis1: 20, axis2: 10 }
...
```
### Options
- `showLegend`: The showLegend keyword is used to show or hide the legend in the radar diagram. The legend is shown by default.
- `max`: The maximum value for the radar diagram. This is used to scale the radar diagram. If not provided, the maximum value is calculated from the data points.
- `min`: The minimum value for the radar diagram. This is used to scale the radar diagram. If not provided, the minimum value is `0`.
- `graticule`: The graticule keyword is used to define the type of graticule to be rendered in the radar diagram. The graticule can be `circle` or `polygon`. If not provided, the default graticule is `circle`.
- `ticks`: The ticks keyword is used to define the number of ticks on the graticule. It is the number of concentric circles or polygons drawn to indicate the scale of the radar diagram. If not provided, the default number of ticks is `5`.
```
radar-beta
...
showLegend true
max 100
min 0
graticule circle
ticks 5
...
```
## Configuration
Please refer to the [configuration](/config/schema-docs/config-defs-radar-diagram-config.html) guide for details.
| Parameter | Description | Default Value |
| --------------- | ---------------------------------------- | ------------- |
| width | Width of the radar diagram | `600` |
| height | Height of the radar diagram | `600` |
| marginTop | Top margin of the radar diagram | `50` |
| marginBottom | Bottom margin of the radar diagram | `50` |
| marginLeft | Left margin of the radar diagram | `50` |
| marginRight | Right margin of the radar diagram | `50` |
| axisScaleFactor | Scale factor for the axis | `1` |
| axisLabelFactor | Factor to adjust the axis label position | `1.05` |
| curveTension | Tension for the rounded curves | `0.17` |
## Theme Variables
### Global Theme Variables
> **Note**
> The default values for these variables depend on the theme used. To override the default values, set the desired values in the themeVariables section of the configuration:
>
> ---
>
> config:
> themeVariables:
> cScale0: "#FF0000"
> cScale1: "#00FF00"
>
> ---
Radar charts support the color scales `cScale${i}` where `i` is a number from `0` to the theme's maximum number of colors in its color scale. Usually, the maximum number of colors is `12`.
| Property | Description |
| ---------- | ------------------------------ |
| fontSize | Font size of the title |
| titleColor | Color of the title |
| cScale${i} | Color scale for the i-th curve |
### Radar Style Options
> **Note**
> Specific variables for radar resides inside the `radar` key. To set the radar style options, use this syntax.
>
> ---
>
> config:
> themeVariables:
> radar:
> axisColor: "#FF0000"
>
> ---
| Property | Description | Default Value |
| -------------------- | ---------------------------- | ------------- |
| axisColor | Color of the axis lines | `black` |
| axisStrokeWidth | Width of the axis lines | `1` |
| axisLabelFontSize | Font size of the axis labels | `12px` |
| curveOpacity | Opacity of the curves | `0.7` |
| curveStrokeWidth | Width of the curves | `2` |
| graticuleColor | Color of the graticule | `black` |
| graticuleOpacity | Opacity of the graticule | `0.5` |
| graticuleStrokeWidth | Width of the graticule | `1` |
| legendBoxSize | Size of the legend box | `10` |
| legendFontSize | Font size of the legend | `14px` |
## Example on config and theme
```mermaid-example
---
config:
radar:
axisScaleFactor: 0.25
curveTension: 0.1
theme: base
themeVariables:
cScale0: "#FF0000"
cScale1: "#00FF00"
cScale2: "#0000FF"
radar:
curveOpacity: 0
---
radar-beta
axis A, B, C, D, E
curve c1{1,2,3,4,5}
curve c2{5,4,3,2,1}
curve c3{3,3,3,3,3}
```
```mermaid
---
config:
radar:
axisScaleFactor: 0.25
curveTension: 0.1
theme: base
themeVariables:
cScale0: "#FF0000"
cScale1: "#00FF00"
cScale2: "#0000FF"
radar:
curveOpacity: 0
---
radar-beta
axis A, B, C, D, E
curve c1{1,2,3,4,5}
curve c2{5,4,3,2,1}
curve c3{3,3,3,3,3}
```
<!--- cspell:ignore Kiviat --->
@@ -0,0 +1,495 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/requirementDiagram.md](../../packages/mermaid/src/docs/syntax/requirementDiagram.md).
# Requirement Diagram
> A Requirement diagram provides a visualization for requirements and their connections, to each other and other documented elements. The modeling specs follow those defined by SysML v1.6.
Rendering requirements is straightforward.
```mermaid-example
requirementDiagram
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
element test_entity {
type: simulation
}
test_entity - satisfies -> test_req
```
```mermaid
requirementDiagram
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
element test_entity {
type: simulation
}
test_entity - satisfies -> test_req
```
## Syntax
There are three types of components to a requirement diagram: requirement, element, and relationship.
The grammar for defining each is defined below. Words denoted in angle brackets, such as `<word>`, are enumerated keywords that have options elaborated in a table. `user_defined_...` is use in any place where user input is expected.
An important note on user text: all input can be surrounded in quotes or not. For example, both `id: "here is an example"` and `id: here is an example` are both valid. However, users must be careful with unquoted input. The parser will fail if another keyword is detected.
### Requirement
A requirement definition contains a requirement type, name, id, text, risk, and verification method. The syntax follows:
```
<type> user_defined_name {
id: user_defined_id
text: user_defined text
risk: <risk>
verifymethod: <method>
}
```
Type, risk, and method are enumerations defined in SysML.
| Keyword | Options |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| Type | requirement, functionalRequirement, interfaceRequirement, performanceRequirement, physicalRequirement, designConstraint |
| Risk | Low, Medium, High |
| VerificationMethod | Analysis, Inspection, Test, Demonstration |
### Element
An element definition contains an element name, type, and document reference. These three are all user defined. The element feature is intended to be lightweight but allow requirements to be connected to portions of other documents.
```
element user_defined_name {
type: user_defined_type
docref: user_defined_ref
}
```
### Markdown Formatting
In places where user defined text is possible (like names, requirement text, element docref, etc.), you can:
- Surround the text in quotes: `"example text"`
- Use markdown formatting inside quotes: `"**bold text** and *italics*"`
Example:
```mermaid-example
requirementDiagram
requirement "__test_req__" {
id: 1
text: "*italicized text* **bold text**"
risk: high
verifymethod: test
}
```
```mermaid
requirementDiagram
requirement "__test_req__" {
id: 1
text: "*italicized text* **bold text**"
risk: high
verifymethod: test
}
```
### Relationship
Relationships are comprised of a source node, destination node, and relationship type.
Each follows the definition format of
```
{name of source} - <type> -> {name of destination}
```
or
```
{name of destination} <- <type> - {name of source}
```
"name of source" and "name of destination" should be names of requirement or element nodes defined elsewhere.
A relationship type can be one of contains, copies, derives, satisfies, verifies, refines, or traces.
Each relationship is labeled in the diagram.
## Larger Example
This example uses all features of the diagram.
```mermaid-example
requirementDiagram
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
functionalRequirement test_req2 {
id: 1.1
text: the second test text.
risk: low
verifymethod: inspection
}
performanceRequirement test_req3 {
id: 1.2
text: the third test text.
risk: medium
verifymethod: demonstration
}
interfaceRequirement test_req4 {
id: 1.2.1
text: the fourth test text.
risk: medium
verifymethod: analysis
}
physicalRequirement test_req5 {
id: 1.2.2
text: the fifth test text.
risk: medium
verifymethod: analysis
}
designConstraint test_req6 {
id: 1.2.3
text: the sixth test text.
risk: medium
verifymethod: analysis
}
element test_entity {
type: simulation
}
element test_entity2 {
type: word doc
docRef: reqs/test_entity
}
element test_entity3 {
type: "test suite"
docRef: github.com/all_the_tests
}
test_entity - satisfies -> test_req2
test_req - traces -> test_req2
test_req - contains -> test_req3
test_req3 - contains -> test_req4
test_req4 - derives -> test_req5
test_req5 - refines -> test_req6
test_entity3 - verifies -> test_req5
test_req <- copies - test_entity2
```
```mermaid
requirementDiagram
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
functionalRequirement test_req2 {
id: 1.1
text: the second test text.
risk: low
verifymethod: inspection
}
performanceRequirement test_req3 {
id: 1.2
text: the third test text.
risk: medium
verifymethod: demonstration
}
interfaceRequirement test_req4 {
id: 1.2.1
text: the fourth test text.
risk: medium
verifymethod: analysis
}
physicalRequirement test_req5 {
id: 1.2.2
text: the fifth test text.
risk: medium
verifymethod: analysis
}
designConstraint test_req6 {
id: 1.2.3
text: the sixth test text.
risk: medium
verifymethod: analysis
}
element test_entity {
type: simulation
}
element test_entity2 {
type: word doc
docRef: reqs/test_entity
}
element test_entity3 {
type: "test suite"
docRef: github.com/all_the_tests
}
test_entity - satisfies -> test_req2
test_req - traces -> test_req2
test_req - contains -> test_req3
test_req3 - contains -> test_req4
test_req4 - derives -> test_req5
test_req5 - refines -> test_req6
test_entity3 - verifies -> test_req5
test_req <- copies - test_entity2
```
## Direction
The diagram can be rendered in different directions using the `direction` statement. Valid values are:
- `TB` - Top to Bottom (default)
- `BT` - Bottom to Top
- `LR` - Left to Right
- `RL` - Right to Left
Example:
```mermaid-example
requirementDiagram
direction LR
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
element test_entity {
type: simulation
}
test_entity - satisfies -> test_req
```
```mermaid
requirementDiagram
direction LR
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
element test_entity {
type: simulation
}
test_entity - satisfies -> test_req
```
## Styling
Requirements and elements can be styled using direct styling or classes. As a rule of thumb, when applying styles or classes, it accepts a list of requirement or element names and a list of class names allowing multiple assignments at a time (The only exception is the shorthand syntax `:::` which can assign multiple classes but only to one requirement or element at a time).
### Direct Styling
Use the `style` keyword to apply CSS styles directly:
```mermaid-example
requirementDiagram
requirement test_req {
id: 1
text: styling example
risk: low
verifymethod: test
}
element test_entity {
type: simulation
}
style test_req fill:#ffa,stroke:#000, color: green
style test_entity fill:#f9f,stroke:#333, color: blue
```
```mermaid
requirementDiagram
requirement test_req {
id: 1
text: styling example
risk: low
verifymethod: test
}
element test_entity {
type: simulation
}
style test_req fill:#ffa,stroke:#000, color: green
style test_entity fill:#f9f,stroke:#333, color: blue
```
### Class Definitions
Define reusable styles using `classDef`:
```mermaid-example
requirementDiagram
requirement test_req {
id: 1
text: "class styling example"
risk: low
verifymethod: test
}
element test_entity {
type: simulation
}
classDef important fill:#f96,stroke:#333,stroke-width:4px
classDef test fill:#ffa,stroke:#000
```
```mermaid
requirementDiagram
requirement test_req {
id: 1
text: "class styling example"
risk: low
verifymethod: test
}
element test_entity {
type: simulation
}
classDef important fill:#f96,stroke:#333,stroke-width:4px
classDef test fill:#ffa,stroke:#000
```
### Default class
If a class is named default it will be applied to all nodes. Specific styles and classes should be defined afterwards to override the applied default styling.
```
classDef default fill:#f9f,stroke:#333,stroke-width:4px;
```
### Applying Classes
Classes can be applied in two ways:
1. Using the `class` keyword:
```
class test_req,test_entity important
```
2. Using the shorthand syntax with `:::` either during the definition or afterwards:
```
requirement test_req:::important {
id: 1
text: class styling example
risk: low
verifymethod: test
}
```
```
element test_elem {
}
test_elem:::myClass
```
### Combined Example
```mermaid-example
requirementDiagram
requirement test_req:::important {
id: 1
text: "class styling example"
risk: low
verifymethod: test
}
element test_entity {
type: simulation
}
classDef important font-weight:bold
class test_entity important
style test_entity fill:#f9f,stroke:#333
```
```mermaid
requirementDiagram
requirement test_req:::important {
id: 1
text: "class styling example"
risk: low
verifymethod: test
}
element test_entity {
type: simulation
}
classDef important font-weight:bold
class test_entity important
style test_entity fill:#f9f,stroke:#333
```
<!--- cspell:ignore reqs --->
+305
View File
@@ -0,0 +1,305 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/sankey.md](../../packages/mermaid/src/docs/syntax/sankey.md).
# Sankey diagram (v10.3.0+)
> A sankey diagram is a visualization used to depict a flow from one set of values to another.
> **Warning**
> This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future.
The things being connected are called nodes and the connections are called links.
## Example
This example taken from [observable](https://observablehq.com/@d3/sankey/2?collection=@d3/d3-sankey). It may be rendered a little bit differently, though, in terms of size and colors.
```mermaid-example
---
config:
sankey:
showValues: false
---
sankey
Agricultural 'waste',Bio-conversion,124.729
Bio-conversion,Liquid,0.597
Bio-conversion,Losses,26.862
Bio-conversion,Solid,280.322
Bio-conversion,Gas,81.144
Biofuel imports,Liquid,35
Biomass imports,Solid,35
Coal imports,Coal,11.606
Coal reserves,Coal,63.965
Coal,Solid,75.571
District heating,Industry,10.639
District heating,Heating and cooling - commercial,22.505
District heating,Heating and cooling - homes,46.184
Electricity grid,Over generation / exports,104.453
Electricity grid,Heating and cooling - homes,113.726
Electricity grid,H2 conversion,27.14
Electricity grid,Industry,342.165
Electricity grid,Road transport,37.797
Electricity grid,Agriculture,4.412
Electricity grid,Heating and cooling - commercial,40.858
Electricity grid,Losses,56.691
Electricity grid,Rail transport,7.863
Electricity grid,Lighting & appliances - commercial,90.008
Electricity grid,Lighting & appliances - homes,93.494
Gas imports,Ngas,40.719
Gas reserves,Ngas,82.233
Gas,Heating and cooling - commercial,0.129
Gas,Losses,1.401
Gas,Thermal generation,151.891
Gas,Agriculture,2.096
Gas,Industry,48.58
Geothermal,Electricity grid,7.013
H2 conversion,H2,20.897
H2 conversion,Losses,6.242
H2,Road transport,20.897
Hydro,Electricity grid,6.995
Liquid,Industry,121.066
Liquid,International shipping,128.69
Liquid,Road transport,135.835
Liquid,Domestic aviation,14.458
Liquid,International aviation,206.267
Liquid,Agriculture,3.64
Liquid,National navigation,33.218
Liquid,Rail transport,4.413
Marine algae,Bio-conversion,4.375
Ngas,Gas,122.952
Nuclear,Thermal generation,839.978
Oil imports,Oil,504.287
Oil reserves,Oil,107.703
Oil,Liquid,611.99
Other waste,Solid,56.587
Other waste,Bio-conversion,77.81
Pumped heat,Heating and cooling - homes,193.026
Pumped heat,Heating and cooling - commercial,70.672
Solar PV,Electricity grid,59.901
Solar Thermal,Heating and cooling - homes,19.263
Solar,Solar Thermal,19.263
Solar,Solar PV,59.901
Solid,Agriculture,0.882
Solid,Thermal generation,400.12
Solid,Industry,46.477
Thermal generation,Electricity grid,525.531
Thermal generation,Losses,787.129
Thermal generation,District heating,79.329
Tidal,Electricity grid,9.452
UK land based bioenergy,Bio-conversion,182.01
Wave,Electricity grid,19.013
Wind,Electricity grid,289.366
```
```mermaid
---
config:
sankey:
showValues: false
---
sankey
Agricultural 'waste',Bio-conversion,124.729
Bio-conversion,Liquid,0.597
Bio-conversion,Losses,26.862
Bio-conversion,Solid,280.322
Bio-conversion,Gas,81.144
Biofuel imports,Liquid,35
Biomass imports,Solid,35
Coal imports,Coal,11.606
Coal reserves,Coal,63.965
Coal,Solid,75.571
District heating,Industry,10.639
District heating,Heating and cooling - commercial,22.505
District heating,Heating and cooling - homes,46.184
Electricity grid,Over generation / exports,104.453
Electricity grid,Heating and cooling - homes,113.726
Electricity grid,H2 conversion,27.14
Electricity grid,Industry,342.165
Electricity grid,Road transport,37.797
Electricity grid,Agriculture,4.412
Electricity grid,Heating and cooling - commercial,40.858
Electricity grid,Losses,56.691
Electricity grid,Rail transport,7.863
Electricity grid,Lighting & appliances - commercial,90.008
Electricity grid,Lighting & appliances - homes,93.494
Gas imports,Ngas,40.719
Gas reserves,Ngas,82.233
Gas,Heating and cooling - commercial,0.129
Gas,Losses,1.401
Gas,Thermal generation,151.891
Gas,Agriculture,2.096
Gas,Industry,48.58
Geothermal,Electricity grid,7.013
H2 conversion,H2,20.897
H2 conversion,Losses,6.242
H2,Road transport,20.897
Hydro,Electricity grid,6.995
Liquid,Industry,121.066
Liquid,International shipping,128.69
Liquid,Road transport,135.835
Liquid,Domestic aviation,14.458
Liquid,International aviation,206.267
Liquid,Agriculture,3.64
Liquid,National navigation,33.218
Liquid,Rail transport,4.413
Marine algae,Bio-conversion,4.375
Ngas,Gas,122.952
Nuclear,Thermal generation,839.978
Oil imports,Oil,504.287
Oil reserves,Oil,107.703
Oil,Liquid,611.99
Other waste,Solid,56.587
Other waste,Bio-conversion,77.81
Pumped heat,Heating and cooling - homes,193.026
Pumped heat,Heating and cooling - commercial,70.672
Solar PV,Electricity grid,59.901
Solar Thermal,Heating and cooling - homes,19.263
Solar,Solar Thermal,19.263
Solar,Solar PV,59.901
Solid,Agriculture,0.882
Solid,Thermal generation,400.12
Solid,Industry,46.477
Thermal generation,Electricity grid,525.531
Thermal generation,Losses,787.129
Thermal generation,District heating,79.329
Tidal,Electricity grid,9.452
UK land based bioenergy,Bio-conversion,182.01
Wave,Electricity grid,19.013
Wind,Electricity grid,289.366
```
## Syntax
The idea behind syntax is that a user types `sankey` keyword first, then pastes raw CSV below and get the result.
It implements CSV standard as [described here](https://www.ietf.org/rfc/rfc4180.txt) with subtle **differences**:
- CSV must contain **3 columns only**
- It is **allowed** to have **empty lines** without comma separators for visual purposes
### Basic
It is implied that 3 columns inside CSV should represent `source`, `target` and `value` accordingly:
```mermaid-example
sankey
%% source,target,value
Electricity grid,Over generation / exports,104.453
Electricity grid,Heating and cooling - homes,113.726
Electricity grid,H2 conversion,27.14
```
```mermaid
sankey
%% source,target,value
Electricity grid,Over generation / exports,104.453
Electricity grid,Heating and cooling - homes,113.726
Electricity grid,H2 conversion,27.14
```
### Empty Lines
CSV does not support empty lines without comma delimiters by default. But you can add them if needed:
```mermaid-example
sankey
Bio-conversion,Losses,26.862
Bio-conversion,Solid,280.322
Bio-conversion,Gas,81.144
```
```mermaid
sankey
Bio-conversion,Losses,26.862
Bio-conversion,Solid,280.322
Bio-conversion,Gas,81.144
```
### Commas
If you need to have a comma, wrap it in double quotes:
```mermaid-example
sankey
Pumped heat,"Heating and cooling, homes",193.026
Pumped heat,"Heating and cooling, commercial",70.672
```
```mermaid
sankey
Pumped heat,"Heating and cooling, homes",193.026
Pumped heat,"Heating and cooling, commercial",70.672
```
### Double Quotes
If you need to have double quote, put a pair of them inside quoted string:
```mermaid-example
sankey
Pumped heat,"Heating and cooling, ""homes""",193.026
Pumped heat,"Heating and cooling, ""commercial""",70.672
```
```mermaid
sankey
Pumped heat,"Heating and cooling, ""homes""",193.026
Pumped heat,"Heating and cooling, ""commercial""",70.672
```
## Configuration
You can customize link colors, node alignments and diagram dimensions.
```html
<script>
const config = {
startOnLoad: true,
securityLevel: 'loose',
sankey: {
width: 800,
height: 400,
linkColor: 'source',
nodeAlignment: 'left',
},
};
mermaid.initialize(config);
</script>
```
### Links Coloring
You can adjust links' color by setting `linkColor` to one of those:
- `source` - link will be of a source node color
- `target` - link will be of a target node color
- `gradient` - link color will be smoothly transient between source and target node colors
- hex code of color, like `#a1a1a1`
### Node Alignment
Graph layout can be changed by setting `nodeAlignment` to:
- `justify`
- `center`
- `left`
- `right`
<!--- cspell:ignore Ngas bioenergy biofuel --->
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,672 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/stateDiagram.md](../../packages/mermaid/src/docs/syntax/stateDiagram.md).
# State diagrams
> "A state diagram is a type of diagram used in computer science and related fields to describe the behavior of systems.
> State diagrams require that the system described is composed of a finite number of states; sometimes, this is indeed the
> case, while at other times this is a reasonable abstraction." Wikipedia
Mermaid can render state diagrams. The syntax tries to be compliant with the syntax used in plantUml as this will make
it easier for users to share diagrams between mermaid and plantUml.
```mermaid-example
---
title: Simple sample
---
stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
```
```mermaid
---
title: Simple sample
---
stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
```
Older renderer:
```mermaid-example
stateDiagram
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
```
```mermaid
stateDiagram
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
```
In state diagrams systems are described in terms of _states_ and how one _state_ can change to another _state_ via
a _transition._ The example diagram above shows three states: **Still**, **Moving** and **Crash**. You start in the
**Still** state. From **Still** you can change to the **Moving** state. From **Moving** you can change either back to the **Still** state or to
the **Crash** state. There is no transition from **Still** to **Crash**. (You can't crash if you're still.)
## States
A state can be declared in multiple ways. The simplest way is to define a state with just an id:
```mermaid-example
stateDiagram-v2
stateId
```
```mermaid
stateDiagram-v2
stateId
```
Another way is by using the state keyword with a description as per below:
```mermaid-example
stateDiagram-v2
state "This is a state description" as s2
```
```mermaid
stateDiagram-v2
state "This is a state description" as s2
```
Another way to define a state with a description is to define the state id followed by a colon and the description:
```mermaid-example
stateDiagram-v2
s2 : This is a state description
```
```mermaid
stateDiagram-v2
s2 : This is a state description
```
## Transitions
Transitions are path/edges when one state passes into another. This is represented using text arrow, "-->".
When you define a transition between two states and the states are not already defined, the undefined states are defined
with the id from the transition. You can later add descriptions to states defined this way.
```mermaid-example
stateDiagram-v2
s1 --> s2
```
```mermaid
stateDiagram-v2
s1 --> s2
```
It is possible to add text to a transition to describe what it represents:
```mermaid-example
stateDiagram-v2
s1 --> s2: A transition
```
```mermaid
stateDiagram-v2
s1 --> s2: A transition
```
## Start and End
There are two special states indicating the start and stop of the diagram. These are written with the \[\*] syntax and
the direction of the transition to it defines it either as a start or a stop state.
```mermaid-example
stateDiagram-v2
[*] --> s1
s1 --> [*]
```
```mermaid
stateDiagram-v2
[*] --> s1
s1 --> [*]
```
## Composite states
In a real world use of state diagrams you often end up with diagrams that are multidimensional as one state can
have several internal states. These are called composite states in this terminology.
In order to define a composite state you need to use the state keyword followed by an id and the body of the composite
state between {}. You can name a composite state on a separate line just like a simple state. See the example below:
```mermaid-example
stateDiagram-v2
[*] --> First
state First {
[*] --> second
second --> [*]
}
[*] --> NamedComposite
NamedComposite: Another Composite
state NamedComposite {
[*] --> namedSimple
namedSimple --> [*]
namedSimple: Another simple
}
```
```mermaid
stateDiagram-v2
[*] --> First
state First {
[*] --> second
second --> [*]
}
[*] --> NamedComposite
NamedComposite: Another Composite
state NamedComposite {
[*] --> namedSimple
namedSimple --> [*]
namedSimple: Another simple
}
```
You can do this in several layers:
```mermaid-example
stateDiagram-v2
[*] --> First
state First {
[*] --> Second
state Second {
[*] --> second
second --> Third
state Third {
[*] --> third
third --> [*]
}
}
}
```
```mermaid
stateDiagram-v2
[*] --> First
state First {
[*] --> Second
state Second {
[*] --> second
second --> Third
state Third {
[*] --> third
third --> [*]
}
}
}
```
You can also define transitions also between composite states:
```mermaid-example
stateDiagram-v2
[*] --> First
First --> Second
First --> Third
state First {
[*] --> fir
fir --> [*]
}
state Second {
[*] --> sec
sec --> [*]
}
state Third {
[*] --> thi
thi --> [*]
}
```
```mermaid
stateDiagram-v2
[*] --> First
First --> Second
First --> Third
state First {
[*] --> fir
fir --> [*]
}
state Second {
[*] --> sec
sec --> [*]
}
state Third {
[*] --> thi
thi --> [*]
}
```
_You cannot define transitions between internal states belonging to different composite states_
## Choice
Sometimes you need to model a choice between two or more paths, you can do so using <\<choice>>.
```mermaid-example
stateDiagram-v2
state if_state <<choice>>
[*] --> IsPositive
IsPositive --> if_state
if_state --> False: if n < 0
if_state --> True : if n >= 0
```
```mermaid
stateDiagram-v2
state if_state <<choice>>
[*] --> IsPositive
IsPositive --> if_state
if_state --> False: if n < 0
if_state --> True : if n >= 0
```
## Forks
It is possible to specify a fork in the diagram using <\<fork>> <\<join>>.
```mermaid-example
stateDiagram-v2
state fork_state <<fork>>
[*] --> fork_state
fork_state --> State2
fork_state --> State3
state join_state <<join>>
State2 --> join_state
State3 --> join_state
join_state --> State4
State4 --> [*]
```
```mermaid
stateDiagram-v2
state fork_state <<fork>>
[*] --> fork_state
fork_state --> State2
fork_state --> State3
state join_state <<join>>
State2 --> join_state
State3 --> join_state
join_state --> State4
State4 --> [*]
```
## Notes
Sometimes nothing says it better than a Post-it note. That is also the case in state diagrams.
Here you can choose to put the note to the _right of_ or to the _left of_ a node.
```mermaid-example
stateDiagram-v2
State1: The state with a note
note right of State1
Important information! You can write
notes.
end note
State1 --> State2
note left of State2 : This is the note to the left.
```
```mermaid
stateDiagram-v2
State1: The state with a note
note right of State1
Important information! You can write
notes.
end note
State1 --> State2
note left of State2 : This is the note to the left.
```
## Concurrency
As in plantUml you can specify concurrency using the -- symbol.
```mermaid-example
stateDiagram-v2
[*] --> Active
state Active {
[*] --> NumLockOff
NumLockOff --> NumLockOn : EvNumLockPressed
NumLockOn --> NumLockOff : EvNumLockPressed
--
[*] --> CapsLockOff
CapsLockOff --> CapsLockOn : EvCapsLockPressed
CapsLockOn --> CapsLockOff : EvCapsLockPressed
--
[*] --> ScrollLockOff
ScrollLockOff --> ScrollLockOn : EvScrollLockPressed
ScrollLockOn --> ScrollLockOff : EvScrollLockPressed
}
```
```mermaid
stateDiagram-v2
[*] --> Active
state Active {
[*] --> NumLockOff
NumLockOff --> NumLockOn : EvNumLockPressed
NumLockOn --> NumLockOff : EvNumLockPressed
--
[*] --> CapsLockOff
CapsLockOff --> CapsLockOn : EvCapsLockPressed
CapsLockOn --> CapsLockOff : EvCapsLockPressed
--
[*] --> ScrollLockOff
ScrollLockOff --> ScrollLockOn : EvScrollLockPressed
ScrollLockOn --> ScrollLockOff : EvScrollLockPressed
}
```
## Setting the direction of the diagram
With state diagrams you can use the direction statement to set the direction which the diagram will render like in this
example.
```mermaid-example
stateDiagram
direction LR
[*] --> A
A --> B
B --> C
state B {
direction LR
a --> b
}
B --> D
```
```mermaid
stateDiagram
direction LR
[*] --> A
A --> B
B --> C
state B {
direction LR
a --> b
}
B --> D
```
## Comments
Comments can be entered within a state diagram chart, which will be ignored by the parser. Comments need to be on their
own line, and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next
newline will be treated as a comment, including any diagram syntax
```mermaid-example
stateDiagram-v2
[*] --> Still
Still --> [*]
%% this is a comment
Still --> Moving
Moving --> Still %% another comment
Moving --> Crash
Crash --> [*]
```
```mermaid
stateDiagram-v2
[*] --> Still
Still --> [*]
%% this is a comment
Still --> Moving
Moving --> Still %% another comment
Moving --> Crash
Crash --> [*]
```
## Styling with classDefs
As with other diagrams (like flowcharts), you can define a style in the diagram itself and apply that named style to a
state or states in the diagram.
**These are the current limitations with state diagram classDefs:**
1. Cannot be applied to start or end states
2. Cannot be applied to or within composite states
_These are in development and will be available in a future version._
You define a style using the `classDef` keyword, which is short for "class definition" (where "class" means something
like a _CSS class_)
followed by _a name for the style,_
and then one or more _property-value pairs_. Each _property-value pair_ is
a _[valid CSS property name](https://www.w3.org/TR/CSS/#properties)_ followed by a colon (`:`) and then a _value._
Here is an example of a classDef with just one property-value pair:
```txt
classDef movement font-style:italic;
```
where
- the _name_ of the style is `movement`
- the only _property_ is `font-style` and its _value_ is `italic`
If you want to have more than one _property-value pair_ then you put a comma (`,`) between each _property-value pair._
Here is an example with three property-value pairs:
```txt
classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
```
where
- the _name_ of the style is `badBadEvent`
- the first _property_ is `fill` and its _value_ is `#f00`
- the second _property_ is `color` and its _value_ is `white`
- the third _property_ is `font-weight` and its _value_ is `bold`
- the fourth _property_ is `stroke-width` and its _value_ is `2px`
- the fifth _property_ is `stroke` and its _value_ is `yellow`
### Apply classDef styles to states
There are two ways to apply a `classDef` style to a state:
1. use the `class` keyword to apply a classDef style to one or more states in a single statement, or
2. use the `:::` operator to apply a classDef style to a state as it is being used in a transition statement (e.g. with an arrow
to/from another state)
#### 1. `class` statement
A `class` statement tells Mermaid to apply the named classDef to one or more classes. The form is:
```txt
class [one or more state names, separated by commas] [name of a style defined with classDef]
```
Here is an example applying the `badBadEvent` style to a state named `Crash`:
```txt
class Crash badBadEvent
```
Here is an example applying the `movement` style to the two states `Moving` and `Crash`:
```txt
class Moving, Crash movement
```
Here is a diagram that shows the examples in use. Note that the `Crash` state has two classDef styles applied: `movement`
and `badBadEvent`
```mermaid-example
stateDiagram
direction TB
accTitle: This is the accessible title
accDescr: This is an accessible description
classDef notMoving fill:white
classDef movement font-style:italic
classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
[*]--> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
class Still notMoving
class Moving, Crash movement
class Crash badBadEvent
class end badBadEvent
```
```mermaid
stateDiagram
direction TB
accTitle: This is the accessible title
accDescr: This is an accessible description
classDef notMoving fill:white
classDef movement font-style:italic
classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
[*]--> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
class Still notMoving
class Moving, Crash movement
class Crash badBadEvent
class end badBadEvent
```
#### 2. `:::` operator to apply a style to a state
You can apply a classDef style to a state using the `:::` (three colons) operator. The syntax is
```txt
[state]:::[style name]
```
You can use this in a diagram within a statement using a class. This includes the start and end states. For example:
```mermaid-example
stateDiagram
direction TB
accTitle: This is the accessible title
accDescr: This is an accessible description
classDef notMoving fill:white
classDef movement font-style:italic;
classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
[*] --> Still:::notMoving
Still --> [*]
Still --> Moving:::movement
Moving --> Still
Moving --> Crash:::movement
Crash:::badBadEvent --> [*]
```
```mermaid
stateDiagram
direction TB
accTitle: This is the accessible title
accDescr: This is an accessible description
classDef notMoving fill:white
classDef movement font-style:italic;
classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
[*] --> Still:::notMoving
Still --> [*]
Still --> Moving:::movement
Moving --> Still
Moving --> Crash:::movement
Crash:::badBadEvent --> [*]
```
## Spaces in state names
Spaces can be added to a state by first defining the state with an id and then referencing the id later.
In the following example there is a state with the id **yswsii** and description **Your state with spaces in it**.
After it has been defined, **yswsii** is used in the diagram in the first transition (`[*] --> yswsii`)
and also in the transition to **YetAnotherState** (`yswsii --> YetAnotherState`).
(**yswsii** has been styled so that it is different from the other states.)
```mermaid-example
stateDiagram
classDef yourState font-style:italic,font-weight:bold,fill:white
yswsii: Your state with spaces in it
[*] --> yswsii:::yourState
[*] --> SomeOtherState
SomeOtherState --> YetAnotherState
yswsii --> YetAnotherState
YetAnotherState --> [*]
```
```mermaid
stateDiagram
classDef yourState font-style:italic,font-weight:bold,fill:white
yswsii: Your state with spaces in it
[*] --> yswsii:::yourState
[*] --> SomeOtherState
SomeOtherState --> YetAnotherState
yswsii --> YetAnotherState
YetAnotherState --> [*]
```
<!--- cspell:ignore yswsii --->
@@ -0,0 +1,540 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/timeline.md](../../packages/mermaid/src/docs/syntax/timeline.md).
# Timeline Diagram
> Timeline: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stable except for the icon integration which is the experimental part.
"A timeline is a type of diagram used to illustrate a chronology of events, dates, or periods of time. It is usually presented graphically to indicate the passing of time, and it is usually organized chronologically. A basic timeline presents a list of events in chronological order, usually using dates as markers. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life" [(Wikipedia)](https://en.wikipedia.org/wiki/Timeline).
### An example of a timeline
```mermaid-example
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook
: Google
2005 : YouTube
2006 : Twitter
```
```mermaid
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook
: Google
2005 : YouTube
2006 : Twitter
```
## Syntax
The syntax for creating Timeline diagram is simple. You always start with the `timeline` keyword to let mermaid know that you want to create a timeline diagram.
After that there is a possibility to add a title to the timeline. This is done by adding a line with the keyword `title` followed by the title text.
Then you add the timeline data, where you always start with a time period, followed by a colon and then the text for the event. Optionally you can add a second colon and then the text for the event. So, you can have one or more events per time period.
```json
{time period} : {event}
```
or
```json
{time period} : {event} : {event}
```
or
```json
{time period} : {event}
: {event}
: {event}
```
**NOTE**: Both time period and event are simple text, and not limited to numbers.
Let us look at the syntax for the example above.
```mermaid-example
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
```
```mermaid
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
```
In this way we can use a text outline to generate a timeline diagram.
The sequence of time period and events is important, as it will be used to draw the timeline. The first time period will be placed at the left side of the timeline, and the last time period will be placed at the right side of the timeline.
Similarly, the first event will be placed at the top for that specific time period, and the last event will be placed at the bottom.
## Grouping of time periods in sections/ages
You can group time periods in sections/ages. This is done by adding a line with the keyword `section` followed by the section name.
All subsequent time periods will be placed in this section until a new section is defined.
If no section is defined, all time periods will be placed in the default section.
Let us look at an example, where we have grouped the time periods in sections.
```mermaid-example
timeline
title Timeline of Industrial Revolution
section 17th-20th century
Industry 1.0 : Machinery, Water power, Steam <br>power
Industry 2.0 : Electricity, Internal combustion engine, Mass production
Industry 3.0 : Electronics, Computers, Automation
section 21st century
Industry 4.0 : Internet, Robotics, Internet of Things
Industry 5.0 : Artificial intelligence, Big data, 3D printing
```
```mermaid
timeline
title Timeline of Industrial Revolution
section 17th-20th century
Industry 1.0 : Machinery, Water power, Steam <br>power
Industry 2.0 : Electricity, Internal combustion engine, Mass production
Industry 3.0 : Electronics, Computers, Automation
section 21st century
Industry 4.0 : Internet, Robotics, Internet of Things
Industry 5.0 : Artificial intelligence, Big data, 3D printing
```
As you can see, the time periods are placed in the sections, and the sections are placed in the order they are defined.
All time periods and events under a given section follow a similar color scheme. This is done to make it easier to see the relationship between time periods and events.
## Wrapping of text for long time-periods or events
By default, the text for time-periods and events will be wrapped if it is too long. This is done to avoid that the text is drawn outside the diagram.
You can also use `<br>` to force a line break.
Let us look at another example, where we have a long time period, and a long event.
```mermaid-example
timeline
title England's History Timeline
section Stone Age
7600 BC : Britain's oldest known house was built in Orkney, Scotland
6000 BC : Sea levels rise and Britain becomes an island.<br> The people who live here are hunter-gatherers.
section Bronze Age
2300 BC : People arrive from Europe and settle in Britain. <br>They bring farming and metalworking.
: New styles of pottery and ways of burying the dead appear.
2200 BC : The last major building works are completed at Stonehenge.<br> People now bury their dead in stone circles.
: The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive.
```
```mermaid
timeline
title England's History Timeline
section Stone Age
7600 BC : Britain's oldest known house was built in Orkney, Scotland
6000 BC : Sea levels rise and Britain becomes an island.<br> The people who live here are hunter-gatherers.
section Bronze Age
2300 BC : People arrive from Europe and settle in Britain. <br>They bring farming and metalworking.
: New styles of pottery and ways of burying the dead appear.
2200 BC : The last major building works are completed at Stonehenge.<br> People now bury their dead in stone circles.
: The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive.
```
```mermaid-example
timeline
title MermaidChart 2023 Timeline
section 2023 Q1 <br> Release Personal Tier
Bullet 1 : sub-point 1a : sub-point 1b
: sub-point 1c
Bullet 2 : sub-point 2a : sub-point 2b
section 2023 Q2 <br> Release XYZ Tier
Bullet 3 : sub-point <br> 3a : sub-point 3b
: sub-point 3c
Bullet 4 : sub-point 4a : sub-point 4b
```
```mermaid
timeline
title MermaidChart 2023 Timeline
section 2023 Q1 <br> Release Personal Tier
Bullet 1 : sub-point 1a : sub-point 1b
: sub-point 1c
Bullet 2 : sub-point 2a : sub-point 2b
section 2023 Q2 <br> Release XYZ Tier
Bullet 3 : sub-point <br> 3a : sub-point 3b
: sub-point 3c
Bullet 4 : sub-point 4a : sub-point 4b
```
## Styling of time periods and events
As explained earlier, each section has a color scheme, and each time period and event under a section follow the similar color scheme.
However, if there is no section defined, then we have two possibilities:
1. Style time periods individually, i.e. each time period(and its corresponding events) will have its own color scheme. This is the DEFAULT behavior.
```mermaid-example
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
```
```mermaid
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
```
**NOTE**: that there are no sections defined, and each time period and its corresponding events will have its own color scheme.
2. Disable the multiColor option using the `disableMultiColor` option. This will make all time periods and events follow the same color scheme.
You will need to add this option either via mermaid.initialize function or directives.
```javascript
mermaid.initialize({
theme: 'base',
startOnLoad: true,
logLevel: 0,
timeline: {
disableMulticolor: false,
},
...
...
```
let us look at same example, where we have disabled the multiColor option.
```mermaid-example
---
config:
logLevel: 'debug'
theme: 'base'
timeline:
disableMulticolor: true
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
```
```mermaid
---
config:
logLevel: 'debug'
theme: 'base'
timeline:
disableMulticolor: true
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
```
### Customizing Color scheme
You can customize the color scheme using the `cScale0` to `cScale11` theme variables, which will change the background colors. Mermaid allows you to set unique colors for up-to 12 sections, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on.
In case you have more than 12 sections, the color scheme will start to repeat.
If you also want to change the foreground color of a section, you can do so use theme variables corresponding `cScaleLabel0` to `cScaleLabel11` variables.
**NOTE**: Default values for these theme variables are picked from the selected theme. If you want to override the default values, you can use the `initialize` call to add your custom theme variable values.
Example:
Now let's override the default values for the `cScale0` to `cScale2` variables:
```mermaid-example
---
config:
logLevel: 'debug'
theme: 'default'
themeVariables:
cScale0: '#ff0000'
cScaleLabel0: '#ffffff'
cScale1: '#00ff00'
cScale2: '#0000ff'
cScaleLabel2: '#ffffff'
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
```mermaid
---
config:
logLevel: 'debug'
theme: 'default'
themeVariables:
cScale0: '#ff0000'
cScaleLabel0: '#ffffff'
cScale1: '#00ff00'
cScale2: '#0000ff'
cScaleLabel2: '#ffffff'
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
See how the colors are changed to the values specified in the theme variables.
## Themes
Mermaid supports a bunch of pre-defined themes which you can use to find the right one for you. PS: you can actually override an existing theme's variable to get your own custom theme going. Learn more about [theming your diagram](../config/theming.md).
The following are the different pre-defined theme options:
- `base`
- `forest`
- `dark`
- `default`
- `neutral`
**NOTE**: To change theme you can either use the `initialize` call or _directives_. Learn more about [directives](../config/directives.md)
Let's put them to use, and see how our sample diagram looks in different themes:
### Base Theme
```mermaid-example
---
config:
logLevel: 'debug'
theme: 'base'
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
```mermaid
---
config:
logLevel: 'debug'
theme: 'base'
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
### Forest Theme
```mermaid-example
---
config:
logLevel: 'debug'
theme: 'forest'
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
```mermaid
---
config:
logLevel: 'debug'
theme: 'forest'
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
### Dark Theme
```mermaid-example
---
config:
logLevel: 'debug'
theme: 'dark'
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
```mermaid
---
config:
logLevel: 'debug'
theme: 'dark'
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
### Default Theme
```mermaid-example
---
config:
logLevel: 'debug'
theme: 'default'
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
```mermaid
---
config:
logLevel: 'debug'
theme: 'default'
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
### Neutral Theme
```mermaid-example
---
config:
logLevel: 'debug'
theme: 'neutral'
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
```mermaid
---
config:
logLevel: 'debug'
theme: 'neutral'
---
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
## Integrating with your library/website
Timeline uses experimental lazy loading & async rendering features which could change in the future.The lazy loading is important in order to be able to add additional diagrams going forward.
You can use this method to add mermaid including the timeline diagram to a web page:
```html
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
</script>
```
You can also refer the [implementation in the live editor](https://github.com/mermaid-js/mermaid-live-editor/blob/develop/src/lib/util/mermaid.ts) to see how the async loading is done.
@@ -0,0 +1,353 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/treemap.md](../../packages/mermaid/src/docs/syntax/treemap.md).
# Treemap Diagram
> A treemap diagram displays hierarchical data as a set of nested rectangles. Each branch of the tree is represented by a rectangle, which is then tiled with smaller rectangles representing sub-branches.
> **Warning**
> This is a new diagram type in Mermaid. Its syntax may evolve in future versions.
## Introduction
Treemap diagrams are an effective way to visualize hierarchical data and show proportions between categories and subcategories. The size of each rectangle is proportional to the value it represents, making it easy to compare different parts of a hierarchy.
Treemap diagrams are particularly useful for:
- Visualizing hierarchical data structures
- Comparing proportions between categories
- Displaying large amounts of hierarchical data in a limited space
- Identifying patterns and outliers in hierarchical data
## Syntax
```
treemap-beta
"Section 1"
"Leaf 1.1": 12
"Section 1.2"
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20
"Leaf 2.2": 25
```
### Node Definition
Nodes in a treemap are defined using the following syntax:
- **Section/Parent nodes**: Defined with quoted text `"Section Name"`
- **Leaf nodes with values**: Defined with quoted text followed by a colon and value `"Leaf Name": value`
- **Hierarchy**: Created using indentation (spaces or tabs)
- **Styling**: Nodes can be styled using the `:::class` syntax
## Examples
### Basic Treemap
```mermaid-example
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
```
```mermaid
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
```
### Hierarchical Treemap
```mermaid-example
treemap-beta
"Products"
"Electronics"
"Phones": 50
"Computers": 30
"Accessories": 20
"Clothing"
"Men's": 40
"Women's": 40
```
```mermaid
treemap-beta
"Products"
"Electronics"
"Phones": 50
"Computers": 30
"Accessories": 20
"Clothing"
"Men's": 40
"Women's": 40
```
### Treemap with Styling
```mermaid-example
treemap-beta
"Section 1"
"Leaf 1.1": 12
"Section 1.2":::class1
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20:::class1
"Leaf 2.2": 25
"Leaf 2.3": 12
classDef class1 fill:red,color:blue,stroke:#FFD600;
```
```mermaid
treemap-beta
"Section 1"
"Leaf 1.1": 12
"Section 1.2":::class1
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20:::class1
"Leaf 2.2": 25
"Leaf 2.3": 12
classDef class1 fill:red,color:blue,stroke:#FFD600;
```
## Styling and Configuration
Treemap diagrams can be customized using Mermaid's styling and configuration options.
### Using classDef for Styling
You can define custom styles for nodes using the `classDef` syntax, which is a standard feature across many Mermaid diagram types:
```mermaid-example
treemap-beta
"Main"
"A": 20
"B":::important
"B1": 10
"B2": 15
"C": 5
classDef important fill:#f96,stroke:#333,stroke-width:2px;
```
```mermaid
treemap-beta
"Main"
"A": 20
"B":::important
"B1": 10
"B2": 15
"C": 5
classDef important fill:#f96,stroke:#333,stroke-width:2px;
```
### Theme Configuration
You can customize the colors of your treemap using the theme configuration:
```mermaid-example
---
config:
theme: 'forest'
---
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
```
```mermaid
---
config:
theme: 'forest'
---
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
```
### Diagram Padding
You can adjust the padding around the treemap diagram using the `diagramPadding` configuration option:
```mermaid-example
---
config:
treemap:
diagramPadding: 200
---
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
```
```mermaid
---
config:
treemap:
diagramPadding: 200
---
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
```
## Configuration Options
The treemap diagram supports the following configuration options:
| Option | Description | Default |
| -------------- | --------------------------------------------------------------------------- | ------- |
| useMaxWidth | When true, the diagram width is set to 100% and scales with available space | true |
| padding | Internal padding between nodes | 10 |
| diagramPadding | Padding around the entire diagram | 8 |
| showValues | Whether to show values in the treemap | true |
| nodeWidth | Width of nodes | 100 |
| nodeHeight | Height of nodes | 40 |
| borderWidth | Width of borders | 1 |
| valueFontSize | Font size for values | 12 |
| labelFontSize | Font size for labels | 14 |
| valueFormat | Format for values (see Value Formatting section) | ',' |
## Advanced Features
### Value Formatting
Values in treemap diagrams can be formatted to display in different ways using the `valueFormat` configuration option. This option primarily uses [D3's format specifiers](https://github.com/d3/d3-format#locale_format) to control how numbers are displayed, with some additional special cases for common formats.
Some common format patterns:
- `,` - Thousands separator (default)
- `$` - Add dollar sign
- `.1f` - Show one decimal place
- `.1%` - Show as percentage with one decimal place
- `$0,0` - Dollar sign with thousands separator
- `$.2f` - Dollar sign with 2 decimal places
- `$,.2f` - Dollar sign with thousands separator and 2 decimal places
The treemap diagram supports both standard D3 format specifiers and some common currency formats that combine the dollar sign with other formatting options.
Example with currency formatting:
```mermaid-example
---
config:
treemap:
valueFormat: '$0,0'
---
treemap-beta
"Budget"
"Operations"
"Salaries": 700000
"Equipment": 200000
"Supplies": 100000
"Marketing"
"Advertising": 400000
"Events": 100000
```
```mermaid
---
config:
treemap:
valueFormat: '$0,0'
---
treemap-beta
"Budget"
"Operations"
"Salaries": 700000
"Equipment": 200000
"Supplies": 100000
"Marketing"
"Advertising": 400000
"Events": 100000
```
Example with percentage formatting:
```mermaid-example
---
config:
treemap:
valueFormat: '$.1%'
---
treemap-beta
"Market Share"
"Company A": 0.35
"Company B": 0.25
"Company C": 0.15
"Others": 0.25
```
```mermaid
---
config:
treemap:
valueFormat: '$.1%'
---
treemap-beta
"Market Share"
"Company A": 0.35
"Company B": 0.25
"Company C": 0.15
"Others": 0.25
```
## Common Use Cases
Treemap diagrams are commonly used for:
1. **Financial Data**: Visualizing budget allocations, market shares, or portfolio compositions
2. **File System Analysis**: Showing disk space usage by folders and files
3. **Population Demographics**: Displaying population distribution across regions and subregions
4. **Product Hierarchies**: Visualizing product categories and their sales volumes
5. **Organizational Structures**: Representing departments and team sizes in a company
## Limitations
- Treemap diagrams work best when the data has a natural hierarchy
- Very small values may be difficult to see or label in a treemap diagram
- Deep hierarchies (many levels) can be challenging to represent clearly
- Treemap diagrams are not well suited for representing data with negative values
## Related Diagrams
If treemap diagrams don't suit your needs, consider these alternatives:
- [**Pie Charts**](./pie.md): For simple proportion comparisons without hierarchy
- **Sunburst Diagrams**: For hierarchical data with a radial layout (yet to be released in Mermaid).
- [**Sankey Diagrams**](./sankey.md): For flow-based hierarchical data
## Notes
The treemap diagram implementation in Mermaid is designed to be simple to use while providing powerful visualization capabilities. As this is a newer diagram type, feedback and feature requests are welcome through the Mermaid GitHub repository.
@@ -0,0 +1,42 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/userJourney.md](../../packages/mermaid/src/docs/syntax/userJourney.md).
# User Journey Diagram
> User journeys describe at a high level of detail exactly what steps different users take to complete a specific task within a system, application or website. This technique shows the current (as-is) user workflow, and reveals areas of improvement for the to-be workflow. (Wikipedia)
Mermaid can render user journey diagrams:
```mermaid-example
journey
title My working day
section Go to work
Make tea: 5: Me
Go upstairs: 3: Me
Do work: 1: Me, Cat
section Go home
Go downstairs: 5: Me
Sit down: 5: Me
```
```mermaid
journey
title My working day
section Go to work
Make tea: 5: Me
Go upstairs: 3: Me
Do work: 1: Me, Cat
section Go home
Go downstairs: 5: Me
Sit down: 5: Me
```
Each user journey is split into sections, these describe the part of the task
the user is trying to complete.
Tasks syntax is `Task name: <score>: <comma separated list of actors>`
Score is a number between 1 and 5, inclusive.
@@ -0,0 +1,250 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/xyChart.md](../../packages/mermaid/src/docs/syntax/xyChart.md).
# XY Chart
> In the context of mermaid-js, the XY chart is a comprehensive charting module that encompasses various types of charts that utilize both x-axis and y-axis for data representation. Presently, it includes two fundamental chart types: the bar chart and the line chart. These charts are designed to visually display and analyze data that involve two numerical variables.
> It's important to note that while the current implementation of mermaid-js includes these two chart types, the framework is designed to be dynamic and adaptable. Therefore, it has the capacity for expansion and the inclusion of additional chart types in the future. This means that users can expect an evolving suite of charting options within the XY chart module, catering to various data visualization needs as new chart types are introduced over time.
## Example
```mermaid-example
xychart
title "Sales Revenue"
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
y-axis "Revenue (in $)" 4000 --> 11000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
```
```mermaid
xychart
title "Sales Revenue"
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
y-axis "Revenue (in $)" 4000 --> 11000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
```
## Syntax
> **Note**
> All text values that contain only one word can be written without `"`. If a text value has many words in it, specifically if it contains spaces, enclose the value in `"`
### Orientations
The chart can be drawn horizontal or vertical, default value is vertical.
```
xychart horizontal
...
```
### Title
The title is a short description of the chart and it will always render on top of the chart.
#### Example
```
xychart
title "This is a simple example"
...
```
> **Note**
> If the title is a single word one no need to use `"`, but if it has space `"` is needed
### x-axis
The x-axis primarily serves as a categorical value, although it can also function as a numeric range value when needed.
#### Example
1. `x-axis title min --> max` x-axis will function as numeric with the given range
2. `x-axis "title with space" [cat1, "cat2 with space", cat3]` x-axis if categorical, categories are text type
### y-axis
The y-axis is employed to represent numerical range values, it cannot have categorical values.
#### Example
1. `y-axis title min --> max`
2. `y-axis title` it will only add the title, the range will be auto generated from data.
> **Note**
> Both x and y axis are optional if not provided we will try to create the range
### Line chart
A line chart offers the capability to graphically depict lines.
#### Example
1. `line [2.3, 45, .98, -3.4]` it can have all valid numeric values.
### Bar chart
A bar chart offers the capability to graphically depict bars.
#### Example
1. `bar [2.3, 45, .98, -3.4]` it can have all valid numeric values.
#### Simplest example
The only two things required are the chart name (`xychart`) and one data set. So you will be able to draw a chart with a simple config like
```
xychart
line [+1.3, .6, 2.4, -.34]
```
## Chart Configurations
| Parameter | Description | Default value |
| ------------------------ | ------------------------------------------------------------- | :-----------: |
| width | Width of the chart | 700 |
| height | Height of the chart | 500 |
| titlePadding | Top and Bottom padding of the title | 10 |
| titleFontSize | Title font size | 20 |
| showTitle | Title to be shown or not | true |
| xAxis | xAxis configuration | AxisConfig |
| yAxis | yAxis configuration | AxisConfig |
| chartOrientation | 'vertical' or 'horizontal' | 'vertical' |
| plotReservedSpacePercent | Minimum space plots will take inside the chart | 50 |
| showDataLabel | Should show the value corresponding to the bar within the bar | false |
### AxisConfig
| Parameter | Description | Default value |
| ------------- | ------------------------------------ | :-----------: |
| showLabel | Show axis labels or tick values | true |
| labelFontSize | Font size of the label to be drawn | 14 |
| labelPadding | Top and Bottom padding of the label | 5 |
| showTitle | Axis title to be shown or not | true |
| titleFontSize | Axis title font size | 16 |
| titlePadding | Top and Bottom padding of Axis title | 5 |
| showTick | Tick to be shown or not | true |
| tickLength | How long the tick will be | 5 |
| tickWidth | How width the tick will be | 2 |
| showAxisLine | Axis line to be shown or not | true |
| axisLineWidth | Thickness of the axis line | 2 |
## Chart Theme Variables
Themes for xychart reside inside the `xychart` attribute, allowing customization through the following syntax:
```yaml
---
config:
themeVariables:
xyChart:
titleColor: '#ff0000'
---
```
| Parameter | Description |
| ---------------- | --------------------------------------------------------- |
| backgroundColor | Background color of the whole chart |
| titleColor | Color of the Title text |
| xAxisLabelColor | Color of the x-axis labels |
| xAxisTitleColor | Color of the x-axis title |
| xAxisTickColor | Color of the x-axis tick |
| xAxisLineColor | Color of the x-axis line |
| yAxisLabelColor | Color of the y-axis labels |
| yAxisTitleColor | Color of the y-axis title |
| yAxisTickColor | Color of the y-axis tick |
| yAxisLineColor | Color of the y-axis line |
| plotColorPalette | String of colors separated by comma e.g. "#f3456, #43445" |
### Setting Colors for Lines and Bars
To set the color for lines and bars, use the `plotColorPalette` parameter. Colors in the palette will correspond sequentially to the elements in your chart (e.g., first bar/line will use the first color specified in the palette).
```mermaid-example
---
config:
themeVariables:
xyChart:
plotColorPalette: '#000000, #0000FF, #00FF00, #FF0000'
---
xychart
title "Different Colors in xyChart"
x-axis "categoriesX" ["Category 1", "Category 2", "Category 3", "Category 4"]
y-axis "valuesY" 0 --> 50
%% Black line
line [10,20,30,40]
%% Blue bar
bar [20,30,25,35]
%% Green bar
bar [15,25,20,30]
%% Red line
line [5,15,25,35]
```
```mermaid
---
config:
themeVariables:
xyChart:
plotColorPalette: '#000000, #0000FF, #00FF00, #FF0000'
---
xychart
title "Different Colors in xyChart"
x-axis "categoriesX" ["Category 1", "Category 2", "Category 3", "Category 4"]
y-axis "valuesY" 0 --> 50
%% Black line
line [10,20,30,40]
%% Blue bar
bar [20,30,25,35]
%% Green bar
bar [15,25,20,30]
%% Red line
line [5,15,25,35]
```
## Example on config and theme
```mermaid-example
---
config:
xyChart:
width: 900
height: 600
showDataLabel: true
themeVariables:
xyChart:
titleColor: "#ff0000"
---
xychart
title "Sales Revenue"
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
y-axis "Revenue (in $)" 4000 --> 11000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
```
```mermaid
---
config:
xyChart:
width: 900
height: 600
showDataLabel: true
themeVariables:
xyChart:
titleColor: "#ff0000"
---
xychart
title "Sales Revenue"
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
y-axis "Revenue (in $)" 4000 --> 11000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
```
+474
View File
@@ -0,0 +1,474 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/zenuml.md](../../packages/mermaid/src/docs/syntax/zenuml.md).
# ZenUML
> A Sequence diagram is an interaction diagram that shows how processes operate with one another and in what order.
Mermaid can render sequence diagrams with [ZenUML](https://zenuml.com). Note that ZenUML uses a different
syntax than the original Sequence Diagram in mermaid.
```mermaid-example
zenuml
title Demo
Alice->John: Hello John, how are you?
John->Alice: Great!
Alice->John: See you later!
```
```mermaid
zenuml
title Demo
Alice->John: Hello John, how are you?
John->Alice: Great!
Alice->John: See you later!
```
## Syntax
### Participants
The participants can be defined implicitly as in the first example on this page. The participants or actors are
rendered in order of appearance in the diagram source text. Sometimes you might want to show the participants in a
different order than how they appear in the first message. It is possible to specify the actor's order of
appearance by doing the following:
```mermaid-example
zenuml
title Declare participant (optional)
Bob
Alice
Alice->Bob: Hi Bob
Bob->Alice: Hi Alice
```
```mermaid
zenuml
title Declare participant (optional)
Bob
Alice
Alice->Bob: Hi Bob
Bob->Alice: Hi Alice
```
### Annotators
If you specifically want to use symbols instead of just rectangles with text you can do so by using the annotator syntax to declare participants as per below.
```mermaid-example
zenuml
title Annotators
@Actor Alice
@Database Bob
Alice->Bob: Hi Bob
Bob->Alice: Hi Alice
```
```mermaid
zenuml
title Annotators
@Actor Alice
@Database Bob
Alice->Bob: Hi Bob
Bob->Alice: Hi Alice
```
Here are the available annotators:
![img.png](img/zenuml-participant-annotators.png)
### Aliases
The participants can have a convenient identifier and a descriptive label.
```mermaid-example
zenuml
title Aliases
A as Alice
J as John
A->J: Hello John, how are you?
J->A: Great!
```
```mermaid
zenuml
title Aliases
A as Alice
J as John
A->J: Hello John, how are you?
J->A: Great!
```
## Messages
Messages can be one of:
1. Sync message
2. Async message
3. Creation message
4. Reply message
### Sync message
You can think of a sync (blocking) method in a programming language.
```mermaid-example
zenuml
title Sync message
A.SyncMessage
A.SyncMessage(with, parameters) {
B.nestedSyncMessage()
}
```
```mermaid
zenuml
title Sync message
A.SyncMessage
A.SyncMessage(with, parameters) {
B.nestedSyncMessage()
}
```
### Async message
You can think of an async (non-blocking) method in a programming language.
Fire an event and forget about it.
```mermaid-example
zenuml
title Async message
Alice->Bob: How are you?
```
```mermaid
zenuml
title Async message
Alice->Bob: How are you?
```
### Creation message
We use `new` keyword to create an object.
```mermaid-example
zenuml
new A1
new A2(with, parameters)
```
```mermaid
zenuml
new A1
new A2(with, parameters)
```
### Reply message
There are three ways to express a reply message:
```mermaid-example
zenuml
// 1. assign a variable from a sync message.
a = A.SyncMessage()
// 1.1. optionally give the variable a type
SomeType a = A.SyncMessage()
// 2. use return keyword
A.SyncMessage() {
return result
}
// 3. use @return or @reply annotator on an async message
@return
A->B: result
```
```mermaid
zenuml
// 1. assign a variable from a sync message.
a = A.SyncMessage()
// 1.1. optionally give the variable a type
SomeType a = A.SyncMessage()
// 2. use return keyword
A.SyncMessage() {
return result
}
// 3. use @return or @reply annotator on an async message
@return
A->B: result
```
The third way `@return` is rarely used, but it is useful when you want to return to one level up.
```mermaid-example
zenuml
title Reply message
Client->A.method() {
B.method() {
if(condition) {
return x1
// return early
@return
A->Client: x11
}
}
return x2
}
```
```mermaid
zenuml
title Reply message
Client->A.method() {
B.method() {
if(condition) {
return x1
// return early
@return
A->Client: x11
}
}
return x2
}
```
## Nesting
Sync messages and Creation messages are naturally nestable with `{}`.
```mermaid-example
zenuml
A.method() {
B.nested_sync_method()
B->C: nested async message
}
```
```mermaid
zenuml
A.method() {
B.nested_sync_method()
B->C: nested async message
}
```
## Comments
It is possible to add comments to a sequence diagram with `// comment` syntax.
Comments will be rendered above the messages or fragments. Comments on other places
are ignored. Markdown is supported.
See the example below:
```mermaid-example
zenuml
// a comment on a participant will not be rendered
BookService
// a comment on a message.
// **Markdown** is supported.
BookService.getBook()
```
```mermaid
zenuml
// a comment on a participant will not be rendered
BookService
// a comment on a message.
// **Markdown** is supported.
BookService.getBook()
```
## Loops
It is possible to express loops in a ZenUML diagram. This is done by any of the
following notations:
1. while
2. for
3. forEach, foreach
4. loop
```zenuml
while(condition) {
...statements...
}
```
See the example below:
```mermaid-example
zenuml
Alice->John: Hello John, how are you?
while(true) {
John->Alice: Great!
}
```
```mermaid
zenuml
Alice->John: Hello John, how are you?
while(true) {
John->Alice: Great!
}
```
## Alt
It is possible to express alternative paths in a sequence diagram. This is done by the notation
```zenuml
if(condition1) {
...statements...
} else if(condition2) {
...statements...
} else {
...statements...
}
```
See the example below:
```mermaid-example
zenuml
Alice->Bob: Hello Bob, how are you?
if(is_sick) {
Bob->Alice: Not so good :(
} else {
Bob->Alice: Feeling fresh like a daisy
}
```
```mermaid
zenuml
Alice->Bob: Hello Bob, how are you?
if(is_sick) {
Bob->Alice: Not so good :(
} else {
Bob->Alice: Feeling fresh like a daisy
}
```
## Opt
It is possible to render an `opt` fragment. This is done by the notation
```zenuml
opt {
...statements...
}
```
See the example below:
```mermaid-example
zenuml
Alice->Bob: Hello Bob, how are you?
Bob->Alice: Not so good :(
opt {
Bob->Alice: Thanks for asking
}
```
```mermaid
zenuml
Alice->Bob: Hello Bob, how are you?
Bob->Alice: Not so good :(
opt {
Bob->Alice: Thanks for asking
}
```
## Parallel
It is possible to show actions that are happening in parallel.
This is done by the notation
```zenuml
par {
statement1
statement2
statement3
}
```
See the example below:
```mermaid-example
zenuml
par {
Alice->Bob: Hello guys!
Alice->John: Hello guys!
}
```
```mermaid
zenuml
par {
Alice->Bob: Hello guys!
Alice->John: Hello guys!
}
```
## Try/Catch/Finally (Break)
It is possible to indicate a stop of the sequence within the flow (usually used to model exceptions).
This is done by the notation
```
try {
...statements...
} catch {
...statements...
} finally {
...statements...
}
```
See the example below:
```mermaid-example
zenuml
try {
Consumer->API: Book something
API->BookingService: Start booking process
} catch {
API->Consumer: show failure
} finally {
API->BookingService: rollback status
}
```
```mermaid
zenuml
try {
Consumer->API: Book something
API->BookingService: Start booking process
} catch {
API->Consumer: show failure
} finally {
API->BookingService: rollback status
}
```
## Integrating with your library/website.
Zenuml uses the experimental lazy loading & async rendering features which could change in the future.
You can use this method to add mermaid including the zenuml diagram to a web page:
```html
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
import zenuml from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-zenuml@0.1.0/dist/mermaid-zenuml.esm.min.mjs';
await mermaid.registerExternalDiagrams([zenuml]);
</script>
```
+63
View File
@@ -0,0 +1,63 @@
---
name: q-item-add
description: |
Add a new open question (Q-item) to the registry docs/Открытые_вопросы_v8_3.md.
Use ONLY when customer explicitly requests adding a new business/CTO/legal/design/devops/OPEN
question to the registry. Walks through 6-step workflow: detect section, find next number,
insert entry, update §0 counters, bump header/footer/changelog version, sync §0 row in CLAUDE.md.
disable-model-invocation: true
---
# Q-item-add — добавить новый Q-item в реестр Открытых_вопросов
## Когда использовать
ТОЛЬКО при явном запросе заказчика добавить новый вопрос. Pravila §2.2 — закрытие/добавление вопроса требует явного указания заказчика.
Invoke via `/q-item-add <Биз|CTO|Ю|Диз|DO|OPEN> "<question text>"`.
## Workflow
1. **Detect section.** Открыть `docs/Открытые_вопросы_v8_3.md`, найти секцию по prefix:
- `Биз-*` → section `## 13` (Бизнес).
- `CTO-*` → section `## 3` (CTO/инженерные).
- `Ю-*` → section `## 4` (Юридические).
- `Диз-*` → section `## 5` (Дизайн).
- `DO-*` → section `## 6` (DevOps/инфраструктура).
- `OPEN-*` → section `## 7` (Прочие открытые).
2. **Find next number.** Grep последний номер в секции (e.g., max `Биз-31` → new = `Биз-32`).
```bash
grep -oP '<prefix>-\d+' docs/Открытые_вопросы_v8_3.md | sort -t- -k2 -n | tail -1
```
3. **Insert entry.** Добавить строку формата:
```markdown
**<prefix>-N ⏸** от 2026-MM-DD: <question text>
```
4. **Update §0 «Сводка».** Increment счётчик ⏸ для соответствующего prefix. Шапка `## 0` содержит таблицу типа `Биз 24 ✅ / 7 ⏸` — bump до `8 ⏸`. **Также** «Итого X / Y ✅ / Z ⏸» — bump соответствующие.
5. **Bump versions.** Header (`v1.83 от 13.05.2026 (day +1)` → `v1.84 от 13.05.2026 (day +1)`), footer (last line same), добавить запись в `## 9. История версий`.
6. **Sync CLAUDE.md.** В `CLAUDE.md` §0 row «Открытые вопросы» bump `v1.83+` → `v1.84+`. Помним: CLAUDE.md правится ТОЛЬКО через `/claude-md-management:revise-claude-md` (§5 п.10) — финальный шаг делегируем заказчику или этому skill'у через sub-invocation.
## Validation
После save:
```bash
./bin/lychee.exe --config .lychee.toml docs/Открытые_вопросы_v8_3.md 2>&1 | tail -3
```
Expected: 0 broken links.
Counter arithmetic check: sum of ✅ + ⏸ + 🟦 per prefix = total per prefix.
## Не использовать когда
- Заказчик говорит «закрываем X» — это closure (replace ⏸ → ✅ + дата), не addition. Skip skill, do targeted Edit.
- Item уже существует с тем же текстом — duplicate; уточнить у заказчика или обновить existing.
- Заказчик не давал явного «добавь X в реестр» — Pravila §2.2 запрещает proactive добавление.
+75
View File
@@ -0,0 +1,75 @@
---
name: regression
description: |
Run the project regression sweep and report a canonical status line + GREEN/RED/RED-INCOMPLETE verdict.
Two tiers: `quick` (lint/format/type-check — seconds) and `full` (everything incl.
Pest --parallel, Larastan, Vitest, Vite build, lychee, gitleaks — minutes).
Claude auto-runs only `quick` (e.g. during verification-before-completion);
`full` runs only on explicit `/regression full` or with user confirmation.
---
# Regression — канонический регрессионный свод
## Когда использовать
Перед закрытием задачи/спринта (`full`) или для быстрого фидбэка по ходу работы
(`quick`). Скилл инкапсулирует ~12 команд свода, разбросанных по `package.json`,
`app/package.json`, `app/composer.json` и `lefthook.yml`, в один вызов с
детерминированной канонической строкой и машинным вердиктом.
Invoke via `/regression [quick|full]` (без аргумента → `full`).
## Workflow
1. Определить уровень из аргумента: `quick`, `full`, либо `full` по умолчанию.
2. Запустить через Bash из корня репозитория:
```bash
node .claude/skills/regression/run.mjs <tier>
```
3. Показать пользователю полный вывод скрипта (таблица + каноническая строка +
вердикт + вывод упавших проверок).
4. Интерпретировать вердикт:
- `GREEN` — свод чист, exit-код 0.
- `RED` — перечислены упавшие проверки, exit-код 1; полный вывод каждой —
после вердикта.
- `RED-INCOMPLETE` — проверка не прогналась (нет бинаря), exit-код 1; свод
неполон, зелёным признать нельзя. Если одновременно есть упавшие проверки,
они тоже перечислены в строке вердикта.
## Уровни
- **`quick`** (6 проверок, секунды): Pint, ESLint, Prettier, vue-tsc,
markdownlint, cspell.
- **`full`** (12 проверок, минуты): всё из `quick` + Larastan, Pest `--parallel`,
Vitest, Vite build, lychee, gitleaks.
## Правила инвокации (self-restraint)
- Claude **авто-запускает только `quick`** — в том числе в рамках
`superpowers:verification-before-completion` перед claim «готово» / «passed» /
«closed».
- `full` Claude **сам не запускает** — только по явному `/regression full` от
пользователя ИЛИ запросив подтверждение («запускаю полный свод, ~5–10 мин — ок?»).
- Скилл **не правит `CLAUDE.md`** — он только печатает каноническую строку в
stdout; вставка строки в `CLAUDE.md` — отдельно, через канал
`claude-md-management` (`CLAUDE.md` §5 п.10).
## Caveats
- **Pest `--parallel` flake (квирки 72/73/77).** Если Pest показал 1–3 ошибки,
похожие на Redis-race / cumulative-state / unique-key-collision, — перепрогнать
`full` один раз ИЛИ свериться с агентом `pest-parallel-debugger` до объявления
реального RED.
- **ruflo daemon (квирк 93).** Перед baseline-критичным `full` рассмотреть
`pm2 stop ruflo-daemon` — worker-jitter усиливает Pest-flake.
- gitleaks и lychee: на Windows берутся из `bin\*.exe`, на Linux/Mac CI — из
`PATH`. Отсутствие бинаря → `[⚠] SKIPPED` + вердикт `RED-INCOMPLETE`.
## Не использовать когда
- Нужна одна конкретная проверка — запусти её npm/composer-скрипт напрямую
(быстрее, чем весь свод).
- Pa11y и Semgrep SAST — это CI-tier, в свод намеренно не входят (см. дизайн-спек
`docs/superpowers/specs/2026-05-16-regression-skill-design.md` §5).
+258
View File
@@ -0,0 +1,258 @@
#!/usr/bin/env node
// .claude/skills/regression/run.mjs
// Regression sweep orchestrator for the /regression skill.
// Design: docs/superpowers/specs/2026-05-16-regression-skill-design.md
import { spawnSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import process from 'node:process';
// ── pure: platform binary resolution ───────────────────────────────
export function resolveBinary(name, platform = process.platform) {
return platform === 'win32' ? `bin\\${name}.exe` : name;
}
// ── pure: output header line ───────────────────────────────────────
export function buildHeader(tier) {
const head = `─ /regression ${tier} `;
return head + '─'.repeat(Math.max(3, 48 - head.length));
}
// ── pure: exit-code token ──────────────────────────────────────────
export function parseExit(label, code) {
return `${label} ${code}`;
}
// ── pure: test-count parsers ───────────────────────────────────────
export function parsePest(stdout) {
// pest --parallel emits a single JSON line: {"tool":"pest","result":...,"tests":N,"passed":N,"skipped":N,...}
const jsonMatch = stdout.match(/\{"tool"\s*:\s*"pest"[^}]+\}/);
if (jsonMatch) {
try {
const j = JSON.parse(jsonMatch[0]);
const passed = Number(j.passed ?? 0);
const skipped = Number(j.skipped ?? 0);
const total = Number(j.tests ?? passed + skipped);
const failed = total - passed - skipped;
return `Pest ${total}/${passed}/${skipped}sk/${Math.max(0, failed)}`;
} catch { /* fall through to regex */ }
}
const passed = Number(stdout.match(/(\d+)\s+passed/)?.[1] ?? 0);
const skipped = Number(stdout.match(/(\d+)\s+skipped/)?.[1] ?? 0);
const failed = Number(stdout.match(/(\d+)\s+failed/)?.[1] ?? 0);
return `Pest ${passed + skipped + failed}/${passed}/${skipped}sk/${failed}`;
}
export function parseVitest(stdout) {
const filesLine = stdout.match(/^.*Test Files.+$/m)?.[0] ?? '';
const files = Number(filesLine.match(/(\d+)\s+passed/)?.[1] ?? 0);
const line = stdout.match(/^\s*Tests\s+.+$/m)?.[0] ?? '';
const passed = Number(line.match(/(\d+)\s+passed/)?.[1] ?? 0);
const skipped = Number(line.match(/(\d+)\s+skipped/)?.[1] ?? 0);
const failed = Number(line.match(/(\d+)\s+failed/)?.[1] ?? 0);
return `Vitest ${files}f/${passed}/${skipped}sk/${failed}`;
}
// ── pure: content parsers ──────────────────────────────────────────
export function parseViteBuild(stdout) {
const m = stdout.match(/built in ([\d.]+)\s*s/i);
return `Vite build ${m ? m[1] : '?'}s`;
}
export function parseLarastan(stdout) {
const m = stdout.match(/Found (\d+) error/i);
return `Larastan ${m ? m[1] : 0}`;
}
export function parseGitleaks(stdout, code) {
const commits = stdout.match(/(\d+)\s+commits?\s+scanned/i)?.[1] ?? '?';
const leaks = code === 0
? '0'
: (stdout.match(/(\d+)\s+leaks?\s+found/i)?.[1]
?? stdout.match(/leaks?\s+found:?\s*(\d+)/i)?.[1]
?? '≥1');
return `gitleaks ${leaks}/${commits}`;
}
export function parseLychee(stdout) {
const ok = stdout.match(/(\d+)\s+OK/)?.[1] ?? '?';
const errors = stdout.match(/(\d+)\s+Errors?/i)?.[1] ?? '0';
return `lychee ${ok}/${errors}`;
}
// ── pure: verdict ──────────────────────────────────────────────────
export function computeVerdict(results) {
const skipped = results.filter((r) => r.skipped).map((r) => r.label);
const failed = results
.filter((r) => !r.skipped && r.code !== 0)
.map((r) => r.label);
if (skipped.length) return { verdict: 'RED-INCOMPLETE', exitCode: 1, failed, skipped };
if (failed.length) return { verdict: 'RED', exitCode: 1, failed, skipped };
return { verdict: 'GREEN', exitCode: 0, failed, skipped };
}
// ── pure: output formatting ────────────────────────────────────────
export function buildCanonicalLine(results) {
return results.map((r) => r.token).join(' / ');
}
export function formatRow(r) {
const mark = r.skipped ? '⚠' : r.code === 0 ? '✅' : '❌';
const label = r.label.padEnd(14);
const status = r.skipped
? 'SKIPPED — binary not found'
: `${r.code} ${(r.ms / 1000).toFixed(1)}s`;
return `[${mark}] ${label}${status}`;
}
export function verdictLine(v, total) {
if (v.verdict === 'GREEN') {
return `🟢 GREEN — все ${total} проверок passed`;
}
if (v.verdict === 'RED-INCOMPLETE') {
const tail = v.failed.length ? `; провал: ${v.failed.join(', ')}` : '';
return `🟠 RED-INCOMPLETE — не прогналось: ${v.skipped.join(', ')}${tail}`;
}
return `🔴 RED — ${v.failed.length}/${total} failed: ${v.failed.join(', ')}`;
}
// ── data: checks registry ──────────────────────────────────────────
// Script-based checks carry `cmd`; binary-based checks carry `bin` + `argv`.
// `parse(combinedOutput, exitCode)` → canonical token. `cwd`: '.' = repo root,
// 'app' = the Laravel app. Execution order: quick checks first, then heavy.
export const CHECKS = [
{
id: 'pint', label: 'Pint', tiers: ['quick', 'full'], cwd: 'app',
cmd: 'composer pint:test', parse: (_o, c) => parseExit('Pint', c),
},
{
id: 'eslint', label: 'ESLint', tiers: ['quick', 'full'], cwd: 'app',
cmd: 'npm run lint:vue', parse: (_o, c) => parseExit('ESLint', c),
},
{
id: 'prettier', label: 'Prettier', tiers: ['quick', 'full'], cwd: 'app',
cmd: 'npm run format:check', parse: (_o, c) => parseExit('Prettier', c),
},
{
id: 'vue-tsc', label: 'vue-tsc', tiers: ['quick', 'full'], cwd: 'app',
cmd: 'npm run type-check', parse: (_o, c) => parseExit('vue-tsc', c),
},
{
id: 'markdownlint', label: 'markdownlint', tiers: ['quick', 'full'], cwd: '.',
cmd: 'npm run lint:md', parse: (_o, c) => parseExit('markdownlint', c),
},
{
id: 'cspell', label: 'cspell', tiers: ['quick', 'full'], cwd: '.',
cmd: 'npm run spell', parse: (_o, c) => parseExit('cspell', c),
},
{
id: 'larastan', label: 'Larastan', tiers: ['full'], cwd: 'app',
cmd: 'composer stan', parse: (o) => parseLarastan(o),
},
{
id: 'pest', label: 'Pest', tiers: ['full'], cwd: 'app',
cmd: 'composer test:parallel', parse: (o) => parsePest(o),
},
{
id: 'vitest', label: 'Vitest', tiers: ['full'], cwd: 'app',
cmd: 'npm run test:vue', parse: (o) => parseVitest(o),
},
{
id: 'vite-build', label: 'Vite build', tiers: ['full'], cwd: 'app',
cmd: 'npm run build', parse: (o) => parseViteBuild(o),
},
{
id: 'lychee', label: 'lychee', tiers: ['full'], cwd: '.',
bin: 'lychee',
argv: ['--config', '.lychee.toml', 'docs/**/*.md', 'db/**/*.md', '*.md'],
parse: (o) => parseLychee(o),
},
{
id: 'gitleaks', label: 'gitleaks', tiers: ['full'], cwd: '.',
bin: 'gitleaks',
argv: ['detect', '--source', '.', '--no-banner', '--config', '.gitleaks.toml', '--redact'],
parse: (o, c) => parseGitleaks(o, c),
},
];
// ── I/O: run one check ─────────────────────────────────────────────
function runCheck(check, repoRoot) {
const cwd = check.cwd === '.' ? repoRoot : path.join(repoRoot, check.cwd);
const start = Date.now();
const skippedResult = (reason) => ({
id: check.id, label: check.label, skipped: true, code: null,
ms: Date.now() - start, token: `${check.label} SKIPPED`, stdout: '', stderr: reason,
});
let command;
if (check.bin) {
const bin = resolveBinary(check.bin);
// bin/ executables: existsSync pre-check on Windows (the project ships
// bin\gitleaks.exe / bin\lychee.exe; on POSIX they come from PATH).
if (process.platform === 'win32' && !existsSync(path.join(repoRoot, bin))) {
return skippedResult(`${bin} not found`);
}
command = [bin, ...check.argv].join(' ');
} else {
command = check.cmd;
}
const res = spawnSync(command, {
cwd, shell: true, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024,
});
const ms = Date.now() - start;
// ENOENT (POSIX missing binary), POSIX shell exit 127 ("command not found"),
// or the Windows cmd.exe "is not recognized" message → SKIPPED.
const notFound = (res.error && res.error.code === 'ENOENT')
|| res.status === 127
|| /is not recognized as an internal or external command/i.test(res.stderr ?? '');
if (notFound) {
return skippedResult(`command not found: ${command}`);
}
const stdout = res.stdout ?? '';
const stderr = res.stderr ?? '';
const code = res.status ?? 1;
const token = check.parse(`${stdout}\n${stderr}`, code);
return { id: check.id, label: check.label, skipped: false, code, ms, token, stdout, stderr };
}
// ── orchestrator ───────────────────────────────────────────────────
export function main(argv) {
const tier = argv[0] ?? 'full';
if (tier !== 'quick' && tier !== 'full') {
process.stderr.write(
`regression: unknown argument "${tier}". Usage: run.mjs [quick|full]\n`,
);
process.exitCode = 2;
return;
}
const repoRoot = fileURLToPath(new URL('../../../', import.meta.url));
const checks = CHECKS.filter((c) => c.tiers.includes(tier));
process.stdout.write(`${buildHeader(tier)}\n`);
const results = [];
for (const check of checks) {
const r = runCheck(check, repoRoot);
results.push(r);
process.stdout.write(`${formatRow(r)}\n`);
}
process.stdout.write(`${'─'.repeat(48)}\n`);
process.stdout.write(`Canonical: ${buildCanonicalLine(results)}\n`);
const v = computeVerdict(results);
process.stdout.write(`VERDICT: ${verdictLine(v, results.length)}\n`);
// Full output of failed checks, so failures are visible with file:line.
for (const r of results) {
if (!r.skipped && r.code !== 0) {
process.stdout.write(`\n── ${r.label} output ──\n${r.stdout}\n${r.stderr}\n`);
}
}
process.exitCode = v.exitCode;
}
// Run main only when executed directly (not when imported by run.test.mjs).
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
main(process.argv.slice(2));
}
+213
View File
@@ -0,0 +1,213 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import process from 'node:process';
import {
resolveBinary, buildHeader, parseExit,
parsePest, parseVitest,
parseViteBuild, parseLarastan, parseGitleaks, parseLychee,
computeVerdict,
buildCanonicalLine, formatRow, verdictLine,
CHECKS,
} from './run.mjs';
test('resolveBinary: win32 → bin\\<name>.exe', () => {
assert.equal(resolveBinary('gitleaks', 'win32'), 'bin\\gitleaks.exe');
});
test('resolveBinary: non-win32 → bare name on PATH', () => {
assert.equal(resolveBinary('lychee', 'linux'), 'lychee');
assert.equal(resolveBinary('lychee', 'darwin'), 'lychee');
});
test('buildHeader: starts with the tier banner', () => {
assert.ok(buildHeader('quick').startsWith('─ /regression quick '));
assert.ok(buildHeader('full').startsWith('─ /regression full '));
});
test('buildHeader: is padded with dashes', () => {
assert.ok(buildHeader('full').length >= 30);
});
test('parseExit: builds "<label> <code>" token', () => {
assert.equal(parseExit('Pint', 0), 'Pint 0');
assert.equal(parseExit('ESLint', 1), 'ESLint 1');
});
test('parsePest: passed + skipped, no failures → total derived', () => {
const out = ' Tests: 3 skipped, 739 passed (2104 assertions)\n Duration: 71.23s';
assert.equal(parsePest(out), 'Pest 742/739/3sk/0');
});
test('parsePest: with failures', () => {
const out = ' Tests: 2 failed, 1 skipped, 736 passed (2090 assertions)';
assert.equal(parsePest(out), 'Pest 739/736/1sk/2');
});
test('parsePest: passed only → zeros for skipped/failed', () => {
assert.equal(parsePest(' Tests: 19 passed (44 assertions)'), 'Pest 19/19/0sk/0');
});
test('parsePest: JSON format (pest --parallel) passed + skipped', () => {
const out = '{"tool":"pest","result":"passed","tests":793,"passed":790,"assertions":2391,"duration_ms":32200,"skipped":3}';
assert.equal(parsePest(out), 'Pest 793/790/3sk/0');
});
test('parsePest: JSON format with failures', () => {
const out = '{"tool":"pest","result":"failed","tests":793,"passed":788,"assertions":2380,"duration_ms":31000,"skipped":3}';
assert.equal(parsePest(out), 'Pest 793/788/3sk/2');
});
test('parsePest: JSON format no skipped', () => {
const out = '{"tool":"pest","result":"passed","tests":19,"passed":19,"assertions":44,"duration_ms":1711}';
assert.equal(parsePest(out), 'Pest 19/19/0sk/0');
});
test('parseVitest: files + passed + skipped', () => {
const out = ' Test Files 92 passed (92)\n Tests 774 passed | 3 skipped (777)\n Duration 12.6s';
assert.equal(parseVitest(out), 'Vitest 92f/774/3sk/0');
});
test('parseVitest: with failures, does not confuse "Test Files" with "Tests"', () => {
const out = ' Test Files 2 failed | 90 passed (92)\n Tests 5 failed | 769 passed (774)';
assert.equal(parseVitest(out), 'Vitest 90f/769/0sk/5');
});
test('parseViteBuild: extracts build time', () => {
assert.equal(parseViteBuild('✓ 312 modules transformed.\n✓ built in 2.03s'), 'Vite build 2.03s');
});
test('parseViteBuild: no match → "?"', () => {
assert.equal(parseViteBuild('build crashed'), 'Vite build ?s');
});
test('parseLarastan: clean → 0', () => {
assert.equal(parseLarastan(' [OK] No errors'), 'Larastan 0');
});
test('parseLarastan: counts errors', () => {
assert.equal(parseLarastan(' [ERROR] Found 2 errors'), 'Larastan 2');
});
test('parseGitleaks: clean → 0 leaks', () => {
const out = 'INF 442 commits scanned.\nINF no leaks found';
assert.equal(parseGitleaks(out, 0), 'gitleaks 0/442');
});
test('parseGitleaks: leaks found (non-zero exit)', () => {
const out = 'INF 442 commits scanned.\nWRN 3 leaks found';
assert.equal(parseGitleaks(out, 1), 'gitleaks 3/442');
});
test('parseLychee: OK + errors', () => {
const out = '🔍 325 Total (in 9s)\n✅ 325 OK\n🚫 0 Errors';
assert.equal(parseLychee(out), 'lychee 325/0');
});
test('parseLychee: with broken links', () => {
const out = '🔍 327 Total\n✅ 325 OK\n🚫 2 Errors';
assert.equal(parseLychee(out), 'lychee 325/2');
});
test('computeVerdict: all exit 0 → GREEN, exit code 0', () => {
const v = computeVerdict([
{ label: 'Pint', code: 0, skipped: false },
{ label: 'ESLint', code: 0, skipped: false },
]);
assert.equal(v.verdict, 'GREEN');
assert.equal(v.exitCode, 0);
assert.deepEqual(v.failed, []);
});
test('computeVerdict: one non-zero exit → RED, exit code 1', () => {
const v = computeVerdict([
{ label: 'Pint', code: 0, skipped: false },
{ label: 'Larastan', code: 1, skipped: false },
]);
assert.equal(v.verdict, 'RED');
assert.equal(v.exitCode, 1);
assert.deepEqual(v.failed, ['Larastan']);
});
test('computeVerdict: a skipped check → RED-INCOMPLETE', () => {
const v = computeVerdict([
{ label: 'Pint', code: 0, skipped: false },
{ label: 'gitleaks', code: null, skipped: true },
]);
assert.equal(v.verdict, 'RED-INCOMPLETE');
assert.equal(v.exitCode, 1);
assert.deepEqual(v.skipped, ['gitleaks']);
});
test('computeVerdict: skipped takes precedence over a failure', () => {
const v = computeVerdict([
{ label: 'Larastan', code: 1, skipped: false },
{ label: 'lychee', code: null, skipped: true },
]);
assert.equal(v.verdict, 'RED-INCOMPLETE');
assert.deepEqual(v.failed, ['Larastan']);
assert.deepEqual(v.skipped, ['lychee']);
});
test('buildCanonicalLine: joins tokens in result order with " / "', () => {
const results = [
{ token: 'Pint 0' }, { token: 'ESLint 0' }, { token: 'Pest 742/739/3sk/0' },
];
assert.equal(buildCanonicalLine(results), 'Pint 0 / ESLint 0 / Pest 742/739/3sk/0');
});
test('formatRow: passed check → ✅ mark, label, code, time', () => {
const row = formatRow({ label: 'Pint', code: 0, ms: 1800, skipped: false });
assert.ok(row.startsWith('[✅] Pint'));
assert.ok(row.includes('1.8s'));
});
test('formatRow: failed check → ❌ mark', () => {
assert.ok(formatRow({ label: 'Larastan', code: 1, ms: 8400, skipped: false }).startsWith('[❌] Larastan'));
});
test('formatRow: skipped check → ⚠ mark + SKIPPED', () => {
const row = formatRow({ label: 'gitleaks', code: null, ms: 0, skipped: true });
assert.ok(row.startsWith('[⚠] gitleaks'));
assert.ok(row.includes('SKIPPED'));
});
test('verdictLine: GREEN', () => {
const line = verdictLine({ verdict: 'GREEN', failed: [], skipped: [] }, 12);
assert.ok(line.includes('🟢 GREEN'));
assert.ok(line.includes('12'));
});
test('verdictLine: RED lists failed checks', () => {
const line = verdictLine({ verdict: 'RED', failed: ['Larastan'], skipped: [] }, 12);
assert.ok(line.includes('🔴 RED'));
assert.ok(line.includes('Larastan'));
});
test('verdictLine: RED-INCOMPLETE lists skipped checks', () => {
const line = verdictLine({ verdict: 'RED-INCOMPLETE', failed: [], skipped: ['gitleaks'] }, 12);
assert.ok(line.includes('🟠 RED-INCOMPLETE'));
assert.ok(line.includes('gitleaks'));
});
test('CHECKS: quick tier has exactly 6 checks', () => {
assert.equal(CHECKS.filter((c) => c.tiers.includes('quick')).length, 6);
});
test('CHECKS: full tier has exactly 12 checks', () => {
assert.equal(CHECKS.filter((c) => c.tiers.includes('full')).length, 12);
});
test('CHECKS: quick is a strict subset of full', () => {
const full = new Set(CHECKS.filter((c) => c.tiers.includes('full')).map((c) => c.id));
for (const c of CHECKS.filter((c) => c.tiers.includes('quick'))) {
assert.ok(full.has(c.id), `${c.id} in quick must also be in full`);
}
});
test('CHECKS: every check has id, label, cwd, parse, and a command source', () => {
for (const c of CHECKS) {
assert.ok(c.id && c.label && c.cwd, `${c.id}: id/label/cwd`);
assert.equal(typeof c.parse, 'function', `${c.id}: parse is a function`);
assert.ok(c.cmd || (c.bin && Array.isArray(c.argv)), `${c.id}: has cmd or bin+argv`);
}
});
test('CHECKS: ids are unique', () => {
assert.equal(new Set(CHECKS.map((c) => c.id)).size, CHECKS.length);
});
const RUN = fileURLToPath(new URL('./run.mjs', import.meta.url));
test('main: unknown argument → exit code 2 + error on stderr', () => {
try {
execFileSync(process.execPath, [RUN, 'bogus'], { encoding: 'utf8', stdio: 'pipe' });
assert.fail('expected non-zero exit');
} catch (err) {
assert.equal(err.status, 2);
assert.match(String(err.stderr), /unknown argument/i);
}
});
test('main: importing run.mjs does not auto-run the sweep', () => {
// If the import.meta guard were broken, importing run.mjs at the top of this
// file would have spawned a full sweep. Reaching this assertion proves it did not.
assert.ok(true);
});
+121
View File
@@ -0,0 +1,121 @@
---
name: rls-check
description: |
Verify Row-Level Security on a new or modified table in db/schema.sql.
Use when adding a new table, adding/removing tenant_id column, or modifying
RLS policies. Walks through 7-step checklist (tenant_id, ENABLE RLS, 2+ policies,
5-role GRANTs, db/CHANGELOG_schema.md entry, squawk, smoke test).
For reviewing a diff, branch, or PR with DB changes - use the rls-reviewer agent.
disable-model-invocation: true
---
# RLS-check — verify RLS на таблице
## Когда использовать
При добавлении или модификации таблицы в `db/schema.sql`, особенно перед коммитом. Инкапсулирует 7-item checklist; lefthook pre-commit job 7 (squawk) ловит только часть.
Invoke via `/rls-check <table_name>`.
## Граница с агентом rls-reviewer
`rls-check` (этот скил) и `rls-reviewer` (агент, `.claude/agents/rls-reviewer.md`)
оба проверяют RLS, но в разных ситуациях. Правило выбора:
- Знаешь имя одной конкретной таблицы, проверка вручную перед коммитом →
**`/rls-check <table>`** (этот скил).
- Есть diff / ветка / PR с изменениями БД, набор таблиц заранее не известен →
**агент `rls-reviewer`**.
Скил работает в основном контексте по одной названной таблице и прогоняет
**8 строк вывода** — 7 статических пунктов + живой дымовой тест
(`pest --filter RlsSmokeTest`, шаг 7). Агент работает в отдельном контексте
субагента, разбирает diff/миграцию/PR и прогоняет только **7 статических**
строк — дымовой тест намеренно не запускает.
Первые 7 строк вывода у обоих — общее статическое ядро (tenant_id, ENABLE RLS,
SELECT/ALL политики, GRANT'ы 5 ролей, CHANGELOG, squawk). Это не дублирование:
ядро проверок одно, сценарии вызова разные. Дымовой тест — только в скиле:
запуск Pest в ревью-субагенте медленный и задевает гонки `--parallel`
(квирки 72/77, см. `.claude/agents/pest-parallel-debugger.md`).
## Checklist
1. **tenant_id column.** Grep `db/schema.sql` для `CREATE TABLE <name>`. Verify:
- `tenant_id UUID NOT NULL REFERENCES tenants(id)` присутствует, **OR**
- SaaS-level exemption — explicit comment типа `-- SaaS-level: no tenant_id (justification)`.
```bash
grep -A30 "CREATE TABLE.*\b<name>\b" db/schema.sql | grep -E "tenant_id|SaaS-level"
```
2. **ENABLE RLS.** Должна быть строка `ALTER TABLE <name> ENABLE ROW LEVEL SECURITY;`.
```bash
grep -E "ALTER TABLE\s+<name>\s+ENABLE ROW LEVEL SECURITY" db/schema.sql
```
3. **Policies — минимум 2.**
- SELECT для `crm_app_user`/`crm_app_admin` с tenant scope: `USING (tenant_id = current_setting('app.current_tenant_id')::uuid)`.
- ALL для `crm_app_admin` (или per-table convention).
- SaaS-level: BYPASSRLS role pattern (e.g., `crm_supplier_worker`).
```bash
grep -B1 -A5 "ON <name>" db/schema.sql | grep "POLICY"
```
4. **Role GRANTs.** В `db/02_grants.sql` должны быть GRANT'ы для 5 ролей. Проверить по pattern existing tables.
```bash
grep -E "GRANT.*ON\s+<name>" db/02_grants.sql
```
Expected: ≥5 GRANT statements (по одному на роль) или group GRANT.
5. **CHANGELOG entry.** В `db/CHANGELOG_schema.md` должна быть запись с датой + table name + summary (CLAUDE.md §5 п.8).
```bash
grep "<name>" db/CHANGELOG_schema.md
```
6. **squawk lint.**
```bash
./bin/squawk.exe db/schema.sql 2>&1 | tail -10
```
Expected: exit 0, no issues.
7. **Smoke test.** `tests/Feature/RlsSmokeTest.php` (или новый тест для конкретной таблицы) должен assert'ить, что user в tenant A не видит row из tenant B для новой таблицы.
```bash
cd app && ./vendor/bin/pest --filter RlsSmokeTest 2>&1 | tail -10
```
Expected: all assertions pass.
## Output
Print результат per item + total:
```text
RLS-check: <table_name>
[✅] tenant_id column
[✅] ENABLE RLS
[✅] SELECT policy
[✅] ALL policy
[✅] 5-role GRANTs
[✅] CHANGELOG entry
[✅] squawk passes
[✅] smoke test passes
Pass: 8/8
```
Or failure listing: `[❌] tenant_id column missing — db/schema.sql:NNNN`.
## Не использовать когда
- Modifying existing well-RLS'd table без новых columns — overhead.
- Tables explicitly outside RLS (e.g., Laravel `migrations`, `cache` — internal).
- Проверяешь не одну названную таблицу, а diff/ветку/PR с изменениями БД —
это сценарий агента `rls-reviewer`, не скила.
+85
View File
@@ -0,0 +1,85 @@
name: Accessibility (Pa11y live)
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
a11y:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP 8.3
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: pdo, pdo_pgsql, redis, mbstring, intl, bcmath
coverage: none
- name: Setup Node 20
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install root JS deps
run: npm ci --no-audit --no-fund
- name: Install app composer deps
working-directory: app
run: composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader
- name: Install app JS deps
working-directory: app
run: npm ci --no-audit --no-fund
- name: Bootstrap .env + key
working-directory: app
run: |
cp .env.example .env
php artisan key:generate --force
- name: Prepare SQLite for CI (avoid pg-on-CI fixture cost)
working-directory: app
run: |
touch database/database.sqlite
sed -i 's/DB_CONNECTION=.*/DB_CONNECTION=sqlite/' .env
sed -i 's|DB_DATABASE=.*|DB_DATABASE=/home/runner/work/${{ github.event.repository.name }}/${{ github.event.repository.name }}/app/database/database.sqlite|' .env
- name: Build frontend assets
working-directory: app
run: npm run build
- name: Start Laravel dev-server
working-directory: app
run: nohup php artisan serve --host=127.0.0.1 --port=8000 > /tmp/laravel-serve.log 2>&1 &
- name: Wait for dev-server ready
run: |
for i in {1..30}; do
if curl -s -o /dev/null http://127.0.0.1:8000/login; then
echo "Dev-server up after ${i}s"
exit 0
fi
sleep 1
done
echo "Dev-server did not start within 30s"
tail -50 /tmp/laravel-serve.log
exit 1
- name: Run Pa11y (live Vue)
run: npm run a11y
- name: Upload Pa11y screenshots
if: always()
uses: actions/upload-artifact@v4
with:
name: a11y-screenshots
path: bin/a11y-screenshots/
if-no-files-found: warn
retention-days: 14
+49
View File
@@ -139,3 +139,52 @@ app/infection-summary.log
# Plan 3 Task 5 — Playwright Node subprocess (~200MB chromium downloads on prod)
app/playwright/node_modules/
# Superpowers using-git-worktrees — локальные worktrees вне репо
.claude/worktrees/
# Vitest coverage output (app/coverage/) — генерируется npm run test:coverage
/app/coverage/
# ── Ruflo big-bang integration (2026-05-15) ──────────────────────────────────
# ruflo runtime scaffolding и local-only routing config
.claude-flow/
CLAUDE.local.md
# ruflo runtime state (created on activation 2026-05-15: memory DB + RuVector bridge)
.swarm/
ruvector.db
# CLAUDE.md / .claude/ backups перед npx ruflo init --force (плановые artifacts Task 2.1)
CLAUDE.md.pre-ruflo.bak
.claude.pre-ruflo.bak/
# ruflo install/dry-run logs (transient)
ruflo-init.log
ruflo-init-dryrun.log
ruflo-mcp-stdout.log
ruflo-mcp-stderr.log
# ruflo init --force regen'ит 23 subdirs из upstream IPFS-registry — auto-regenerable, не tracking
.claude/agents/analysis/
.claude/agents/architecture/
.claude/agents/browser/
.claude/agents/consensus/
.claude/agents/core/
.claude/agents/custom/
.claude/agents/data/
.claude/agents/development/
.claude/agents/devops/
.claude/agents/documentation/
.claude/agents/flow-nexus/
.claude/agents/github/
.claude/agents/goal/
.claude/agents/optimization/
.claude/agents/payments/
.claude/agents/sona/
.claude/agents/sparc/
.claude/agents/specialized/
.claude/agents/sublinear/
.claude/agents/swarm/
.claude/agents/templates/
.claude/agents/testing/
.claude/agents/v3/
.claude/commands/*
!.claude/commands/security-review.md
.claude/helpers/
+6
View File
@@ -87,6 +87,12 @@ paths = [
'''app/composer\.lock''',
# Pest-тесты с фиктивными data-фикстурами (не реальные ПДн)
'''app/tests/.*\.php''',
# Database seeders с демо-данными (admin@demo.local + +7916123XXXX демо-телефоны)
'''app/database/seeders/.*\.php''',
# Audit-internal docs (findings/blocked/report/plan) — содержат демо-телефоны и
# script-смешанные artifacts как finding'и для review (не реальные ПДн)
'''docs/superpowers/audits/.*\.md''',
'''docs/superpowers/plans/.*\.md''',
# Mock-данные для UI-разводки фронтенда (фиктивные имена/телефоны)
'''app/resources/js/composables/mockDeals\.ts''',
# Vitest-тесты с assertion на mock-данные (mock-телефоны из mockDeals)
+28 -2
View File
@@ -53,7 +53,22 @@ exclude = [
# web/v8/*.html — статические концепты, root-relative ссылки на будущие маршруты Vue
"^/(login|register|legal|dashboard|deals|admin|reports|reminders|billing|impersonation|notifications)(/|$|\\?)",
# Корневой `/` в концептах (логотип-якорь для будущей главной)
"^/$"
"^/$",
# Plan 3 spec/plan files (commits 1a265b5 + 989256b) используют project-root-
# relative ссылки на app/, db/, lefthook.yml, CLAUDE.md. GitHub UI рендерит
# корректно; lychee local resolution relative к markdown file directory
# ломается (resolved как docs/superpowers/specs/<path>). Links валидны
# семантически, исключаем file://-URLs ведущие в несуществующий docs/-prefix.
"^file://.*/docs/(app|db|lefthook|CLAUDE)",
# Brain-extraction untracked files (другая session, project-root-relative
# paths не resolve'ятся через relative из docs/superpowers/*/). Untracked
# на disk — не входят в commits Plan 3. Исключаем broken file:// URLs.
"^file://.*\\.claude/projects/.*/memory/",
"^file:///C:/[^/]+/[^/]+/CLAUDE\\.md$",
# Brain-extraction file имеет project-root-relative paths типа docs/architecture.md
# которые resolve через docs/superpowers/plans/docs/... — broken для lychee.
"^file://.*docs/superpowers/plans/docs/",
"^file://.*docs/superpowers/plans/CLAUDE\\.md"
]
# Игнорировать файлы вне аудита
@@ -67,7 +82,18 @@ exclude_path = [
# --root-dir, плюс маршруты появятся только после реализации в фазе 2+.
# Линки внутри концептов проверяем визуально при ревью handoff'а.
"liderra_v8_handoff/concepts",
"web/v8"
"web/v8",
# Plan 3 spec + plan files используют project-root-relative paths
# (intended для GitHub UI rendering); lychee resolution относительно
# markdown file directory не работает. Links указывают на реальные
# project files, проверяются визуально при ревью.
"docs/superpowers/specs/2026-05-11-plan3-supplier-sync-design.md",
"docs/superpowers/plans/2026-05-11-supplier-sync-plan3.md",
# Brain-extraction files (другая session, untracked в working tree) —
# имеют ссылки на `docs/...` relative из своей dir + memory/-paths
# вне репозитория. Не относятся к Plan 3 push.
"docs/superpowers/plans/2026-05-10-claude-brain-extraction.md",
"docs/superpowers/specs/2026-05-10-claude-brain-extraction-design.md"
]
# User-Agent — некоторые сайты режут пустой
+2
View File
@@ -2,3 +2,5 @@ node_modules/
.git/
bin/
CLAUDE.md
.claude/skills/mermaid/
.claude/skills/ccpm/
+36 -2
View File
@@ -10,9 +10,10 @@
"type": "http",
"url": "https://api.githubcopilot.com/mcp",
"headers": {
"Authorization": "Bearer ${GITHUB_TOKEN}"
"Authorization": "Bearer ${GITHUB_TOKEN}",
"X-MCP-Toolsets": "actions,code_security,context,dependabot,discussions,gists,issues,notifications,orgs,projects,pull_requests,repos,secret_protection,security_advisories,stargazers,users"
},
"comment": "Фаза 0 #3 — официальный hosted GitHub MCP (https://github.com/github/github-mcp-server). Требует env GITHUB_TOKEN с PAT (scopes: repo, read:org, не давать admin/delete). Раньше использовали deprecated @modelcontextprotocol/server-github — заменён 06.05.2026."
"comment": "Фаза 0 #3 — официальный hosted GitHub MCP (https://github.com/github/github-mcp-server). Требует env GITHUB_TOKEN с PAT (scopes: repo, read:org, не давать admin/delete). Раньше использовали deprecated @modelcontextprotocol/server-github — заменён 06.05.2026. X-MCP-Toolsets явно перечисляет toolset'ы, включая `projects` (GitHub Projects v2 — доски/спринты/milestones) для раздела C9 «Управление проектами» — план docs/superpowers/plans/2026-05-17-c9-project-management-tooling-integration.md (GH1). Заголовок заменяет default-набор: список явный, чтобы не сузить поверхность."
},
"laravel-boost": {
"command": "php",
@@ -23,6 +24,39 @@
"command": "npx",
"args": ["-y", "semgrep-mcp"],
"comment": "Фаза 3 #25 — Semgrep MCP (SAST). Семантический поиск/анализ кода через Semgrep rules в Claude Code. Пакет: npmjs.com/package/semgrep-mcp — если 404, запустить 'npm search semgrep mcp' для актуального имени."
},
"sentry": {
"command": "npx",
"args": ["-y", "@sentry/mcp-server"],
"env": {
"SENTRY_URL": "${SENTRY_URL}",
"SENTRY_AUTH_TOKEN": "${SENTRY_AUTH_TOKEN}"
},
"comment": "Off-phase tool — Sentry MCP для self-hosted экземпляра в Yandex Cloud (CLAUDE.md §2). Pending формализация в Tooling §3.3 #34 — sync нормативки отдельным планом. Package: @sentry/mcp-server@0.33.0+ (official sentry-bot, repo getsentry/sentry-mcp, bin sentry-mcp). Env vars: SENTRY_URL (https://sentry.<your-domain>.ru), SENTRY_AUTH_TOKEN (PAT scope: sentry:read). Credentials в .env.local (gitignored), Claude Code считывает env из shell startup. Если env пустые — MCP server fail gracefully."
},
"redis": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-redis", "redis://localhost:6379"],
"comment": "Off-phase tool — Redis MCP для Memurai (Windows service, Redis 7-совместимый, localhost:6379). Pending формализация в Tooling §3.3 #35 — sync нормативки отдельным планом. Package: @modelcontextprotocol/server-redis@2025.4.25 — DEPRECATED по статусу npm («Package no longer supported»), но Anthropic source, простой протокол, рабочий. Post-MVP migration на community alternative (e.g., @easy-mcps/redis-mcp-server@1.0.8 или @wenit/redis-mcp-server@1.0.3) когда подтвердим trust. READ-ONLY use — отладка очередей, кэша, Pest --parallel race (memory quirk 72). НЕ для prod (нет prod). Если в будущем prod Redis с auth — отдельный entry redis-prod с url через env var."
},
"ruflo": {
"command": "npx",
"args": ["-y", "ruflo@latest", "mcp", "start"],
"comment": "Off-phase orchestration MCP — exposes ~210 ruflo tools (Core/Intelligence/Agents/Memory/DevTools). Package: ruflo v3.7.0-alpha.38+ MIT (npm `ruflo`, repo ruvnet/claude-flow legacy after rename Jan-2026; plugin namespace @claude-flow/*). Plugin discovery via IPFS (CID QmeXmAdbWVvT84GfDXPD2Vg1HWhiTW2VdZfRLhkS96KkX2) — Pinata+Cloudflare gateways flaky 2026-05-15, only ipfs.io reliable. stdio mode (no port-conflict). Big-bang integration per spec/plan 2026-05-15-ruflo-integration-design.md (commit a68a0a0+). Pending формализация в Tooling §4.10 — Phase 3 Task 3.4."
},
"universal-icons": {
"command": "npx",
"args": ["-y", "mcp-universal-icons"],
"comment": "Off-phase A4 design-tooling #45 — Universal Icons MCP (npm mcp-universal-icons, awssat, MIT). Поиск/вставка SVG-иконок из 10 коллекций, включая Lucide (проектный icon-set, CTO-19). Tools: search_icons / get_icon / health_check. SVG framework-neutral по умолчанию — НЕ запрашивать jsx/Tailwind-формат (PSR_v1 R6.0). Формализация — Tooling §4.20. ADR-006 граница UI2: иконки UI; бренд-логотипы — за 21st logo_search. План docs/superpowers/plans/2026-05-17-a4-design-tooling-integration.md."
},
"openapi": {
"command": "npx",
"args": ["-y", "@ivotoby/openapi-mcp-server"],
"env": {
"API_BASE_URL": "http://localhost",
"OPENAPI_SPEC_PATH": "./docs/api/openapi.yaml"
},
"comment": "A3 integration-tooling #47 — OpenAPI MCP (ivo-toby/mcp-openapi-server, @ivotoby/openapi-mcp-server v1.14.0, MIT). Exposes Лидерра REST API endpoints (docs/api/openapi.yaml) as MCP tools. Config via env-vars API_BASE_URL + OPENAPI_SPEC_PATH (stdio transport default). READ scope: API discovery/introspection for Claude Code. Формализован в Tooling §4.22, PSR_v1 R10.1 блок 3, Pravila §13.2."
}
}
}
+93 -17
View File
File diff suppressed because one or more lines are too long
+334
View File
@@ -0,0 +1,334 @@
# Лидерра CRM — Production Deployment Runbook
**Version:** 1.0 от 2026-05-13
**Stack:** Laravel 13 · Vue 3 + Vuetify 3 · PostgreSQL 16 · Redis 7 · PHP 8.3
**Cloud:** Yandex Cloud, region `ru-central1`
---
## 1. System Requirements
| Component | Version | Notes |
|---|---|---|
| PHP | 8.3+ | Extensions: pdo_pgsql, pgsql, redis, bcmath, mbstring, openssl, tokenizer, xml, ctype, fileinfo, pcntl |
| PostgreSQL | 16 | ICU collation support required (`--with-icu` compile flag) |
| Redis | 7.x | Sessions, queues, cache |
| Node.js | 20+ | Frontend build only |
| Composer | 2.x | |
| Supervisor | 4.x | Queue worker process management |
---
## 2. Environment Configuration
Copy `.env.example` to `.env` and set all required values:
```bash
cp app/.env.example app/.env
```
Critical variables:
```ini
APP_ENV=production
APP_KEY= # php artisan key:generate
APP_URL=https://crm.example.com
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=liderra
DB_USERNAME=crm_migrator # migration role — full DDL rights
DB_PASSWORD=<secret>
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=<secret>
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
CACHE_STORE=redis
MAIL_MAILER=smtp
MAIL_HOST=smtp.unisender.com
MAIL_PORT=587
MAIL_USERNAME=<unisender-go-api-key>
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@liderra.ru
MAIL_FROM_NAME="Лидерра"
```
---
## 3. Database Setup
### 3.1 Create database with ICU collation
```sql
-- Run as PostgreSQL superuser
CREATE DATABASE liderra
ENCODING 'UTF8'
LOCALE_PROVIDER 'icu'
ICU_LOCALE 'ru-RU'
TEMPLATE template0;
```
### 3.2 Create application roles
```bash
# Run as PostgreSQL superuser
psql -U postgres liderra < db/00_create_roles.sql
```
The script creates 5 roles: `crm_app_user`, `crm_app_admin`, `crm_readonly`, `crm_migrator`, `crm_supplier_worker` (BYPASSRLS).
### 3.3 Run migrations
```bash
cd app
php artisan migrate --force
```
This loads `db/schema.sql` (v8.19+) via the single bootstrap migration `load_initial_schema.php`.
### 3.4 Apply grants
```bash
psql -U postgres liderra < db/02_grants.sql
```
### 3.5 Create initial partition tables
Partitioned tables (`lead_audit_log`, `supplier_session_log`, etc.) require month-based child partitions to exist before any inserts:
```bash
cd app
php artisan partitions:create-months
```
Run this once after migration. The scheduler maintains partitions automatically thereafter (see §7).
### 3.6 Apply pg_audit extension (pre-prod)
```sql
-- Run as PostgreSQL superuser
CREATE EXTENSION IF NOT EXISTS pgaudit;
```
---
## 4. Application Bootstrap
```bash
cd app
# Install PHP dependencies
composer install --no-dev --optimize-autoloader
# Generate app key (first deploy only)
php artisan key:generate --force
# Clear and cache config/routes/views
php artisan config:cache
php artisan route:cache
php artisan view:cache
# Run storage symlink
php artisan storage:link
```
---
## 5. Frontend Build
```bash
cd app
npm ci
npm run build
```
Output goes to `app/public/build/`. Confirm `app/public/build/manifest.json` exists.
---
## 6. Queue Worker (Supervisor)
Create `/etc/supervisor/conf.d/liderra-worker.conf`:
```ini
[program:liderra-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /path/to/app/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/liderra-worker.log
stopwaitsecs=3600
```
```bash
supervisorctl reread
supervisorctl update
supervisorctl start liderra-worker:*
```
---
## 7. Scheduler (Cron)
Add to the deployment user's crontab (`crontab -e`):
```cron
* * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1
```
The scheduler runs these jobs automatically:
| Command/Job | Schedule | Purpose |
|---|---|---|
| `projects:reset-delivered-today` | daily 00:00 MSK | Reset daily lead counter |
| `projects:reset-monthly` | 1st of month 00:00 MSK | Reset monthly counter for tier lookup |
| `partitions:create-months` | daily | Create PostgreSQL partition tables for upcoming months |
| `RefreshSupplierSessionJob` | hourly + 20:15 MSK | Supplier API session tokens |
| `SyncSupplierProjectsJob` | 20:30 MSK daily | Sync supplier project list |
| `CleanupInactiveSupplierProjectsJob` | 02:00 MSK daily | Archive stale supplier projects |
| `supplier:retry-failed` | hourly | Retry failed supplier lead deliveries |
| `CsvReconcileJob` | hourly | CSV reconciliation (reserve lead intake channel) |
---
## 8. Web Server (Nginx)
```nginx
server {
listen 443 ssl;
server_name crm.example.com;
root /path/to/app/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~* \.(js|css|png|jpg|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
```
---
## 9. Health Checks
```bash
# PHP and Laravel bootstrap
cd app && php artisan about
# Database connection
php artisan db:show
# Scheduler registered entries
php artisan schedule:list
# Queue worker status
supervisorctl status liderra-worker:*
# Redis connection
redis-cli -h 127.0.0.1 ping
# Application HTTP
curl -I https://crm.example.com/login
```
Expected responses:
- `php artisan about` — no errors, APP_ENV=production
- `php artisan schedule:list` — 9 entries including `partitions:create-months`
- Redis: `PONG`
- HTTP `/login`: 200
---
## 10. Deployment Sequence (Rolling Update)
```bash
# 1. Pull latest code
git pull origin main
# 2. Install/update dependencies
cd app && composer install --no-dev --optimize-autoloader
npm ci && npm run build
# 3. Run new migrations (if any)
php artisan migrate --force
# 4. Recache configuration
php artisan config:cache
php artisan route:cache
php artisan view:cache
# 5. Restart queue workers (pick up new code)
supervisorctl restart liderra-worker:*
```
---
## 11. Rollback
```bash
# Revert to previous release tag
git checkout <previous-tag>
cd app
composer install --no-dev --optimize-autoloader
npm ci && npm run build
php artisan migrate:rollback # only if the migration is reversible
php artisan config:cache
supervisorctl restart liderra-worker:*
```
> **Warning:** Schema migrations are not always reversible. Always take a PostgreSQL dump before deploying schema changes.
```bash
pg_dump -U crm_migrator liderra > backup_$(date +%Y%m%d_%H%M%S).sql
```
---
## 12. Development Seed
For staging/dev environments only:
```bash
cd app
php artisan db:seed --class=DemoSeeder --force
```
Creates: `admin@demo.local` / `password`, 3 projects, 14 demo deals.
**Never run DemoSeeder on production.**
---
## 13. Common Issues
| Symptom | Likely Cause | Fix |
|---|---|---|
| `SQLSTATE[08006]` on boot | Wrong `DB_HOST` (use `127.0.0.1`, not `localhost` on Windows) | Set `DB_HOST=127.0.0.1` |
| Partition insert error on new month | `partitions:create-months` not run | `php artisan partitions:create-months` |
| Queue jobs not processing | Supervisor not running or wrong user | `supervisorctl status`; check `stdout_logfile` |
| CSS/JS 404 after deploy | Frontend not rebuilt or `storage:link` missing | `npm run build` + `php artisan storage:link` |
| `jwt expired` from supplier API | Supplier session not refreshed | `php artisan tinker``dispatch(new RefreshSupplierSessionJob)` |
| Scheduler not running | Cron not set up | Verify crontab entry; `php artisan schedule:run --verbose` |
+1 -1
View File
@@ -50,7 +50,7 @@ SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
QUEUE_CONNECTION=redis
CACHE_STORE=database
# CACHE_PREFIX=
+2
View File
@@ -1,10 +1,12 @@
*.log
.backups/
.DS_Store
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
/.deptrac.cache
/.codex
/.cursor/
/.idea
+26
View File
@@ -56,3 +56,29 @@ If you discover a security vulnerability within Laravel, please send an e-mail t
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
## Демо-данные (dev)
Демо-tenant создаётся `DemoSeeder` автоматически при `composer setup` /
`php artisan migrate --seed` в окружениях `local` и `testing`
(см. `DatabaseSeeder` — в `production` DemoSeeder не запускается).
**Учётные данные демо-входа:**
- URL: `/login`
- Email: `admin@demo.local`
- Пароль: `password`
Что создаётся: demo-tenant (`subdomain=demo`, баланс 1000 ₽ / 100 лидов),
admin-пользователь, 3 проекта (сайт/звонок/СМС) и ~14 демо-сделок.
**Пере-сидировать демо-данные** (идемпотентно — повторный запуск не создаёт дублей):
```bash
composer demo:seed
```
Эквивалент: `php artisan db:seed --class=DemoSeeder --force`.
Если при логине демо-аккаунта возвращается 422 — демо-данные не засеяны
на текущей dev-БД (например, после `migrate:fresh`); запустите `composer demo:seed`.
+82
View File
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
/**
* Eloquent cast for PostgreSQL native INT[] columns.
*
* Laravel stock 'array' cast uses json_encode/json_decode and sends `[1,2,3]`
* (JSON), which Postgres rejects on INT[] columns (expects `{1,2,3}` array
* literal). This cast:
*
* - get(): parses Postgres array literal `{1,2,3}` (or empty `{}`) into PHP
* int array.
* - set(): serializes PHP array `[1,2,3]` into Postgres literal `{1,2,3}`.
*
* Used for projects.regions INT[] (Plan 6).
*
* @implements CastsAttributes<list<int>, list<int>|null>
*/
class PostgresIntArray implements CastsAttributes
{
/**
* @param array<string, mixed> $attributes
* @return list<int>
*/
public function get(Model $model, string $key, mixed $value, array $attributes): array
{
if ($value === null || $value === '' || $value === '{}') {
return [];
}
// PG returns literal like "{1,2,3}".
if (is_string($value)) {
$trimmed = trim($value, '{}');
if ($trimmed === '') {
return [];
}
return array_map('intval', explode(',', $trimmed));
}
// Defensive: if driver already gave array.
if (is_array($value)) {
return array_values(array_map('intval', $value));
}
return [];
}
/**
* @param array<string, mixed> $attributes
*/
public function set(Model $model, string $key, mixed $value, array $attributes): ?string
{
if ($value === null) {
return null;
}
// Defensive: interface phpdoc says list<int>|null, but $value is mixed at PHP level;
// protect against runtime misuse (e.g., string passed mistakenly).
// @phpstan-ignore function.alreadyNarrowedType
if (! is_array($value)) {
throw new \InvalidArgumentException(
"PostgresIntArray cast expects array for key '{$key}', got ".gettype($value)
);
}
if ($value === []) {
return '{}';
}
$ints = array_map('intval', $value);
return '{'.implode(',', $ints).'}';
}
}
@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace App\Console\Commands;
use App\Services\MonthlyPartitionManager;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
/**
* Создаёт ежемесячные партиции для `deals` и `supplier_lead_costs`
@@ -30,14 +30,7 @@ class PartitionsCreateMonths extends Command
/** @var string */
protected $description = 'Создаёт ежемесячные партиции deals и supplier_lead_costs на N месяцев вперёд (idempotent)';
/**
* Список таблиц, которые партиционируются по received_at помесячно.
*
* @var array<int, string>
*/
private const PARTITIONED_TABLES = ['deals', 'supplier_lead_costs'];
public function handle(): int
public function handle(MonthlyPartitionManager $manager): int
{
$ahead = max(1, (int) $this->option('ahead'));
$now = Carbon::now()->startOfMonth();
@@ -47,27 +40,17 @@ class PartitionsCreateMonths extends Command
for ($i = 0; $i <= $ahead; $i++) {
$monthStart = $now->copy()->addMonths($i);
$monthEnd = $monthStart->copy()->addMonth();
foreach (self::PARTITIONED_TABLES as $table) {
foreach (MonthlyPartitionManager::PARTITIONED_TABLES as $table) {
$partitionName = sprintf('%s_%s', $table, $monthStart->format('Y_m'));
if ($this->partitionExists($partitionName)) {
if ($manager->ensureMonth($table, $monthStart)) {
$created++;
$this->info(" create <fg=green>{$partitionName}</>");
} else {
$skipped++;
$this->line(" skip <fg=gray>{$partitionName}</> (already exists)");
continue;
}
DB::statement(sprintf(
"CREATE TABLE %s PARTITION OF %s FOR VALUES FROM ('%s') TO ('%s')",
$partitionName,
$table,
$monthStart->format('Y-m-d'),
$monthEnd->format('Y-m-d'),
));
$created++;
$this->info(" create <fg=green>{$partitionName}</> [{$monthStart->format('Y-m-d')}{$monthEnd->format('Y-m-d')})");
}
}
@@ -76,17 +59,4 @@ class PartitionsCreateMonths extends Command
return self::SUCCESS;
}
/**
* Проверка существования партиции через pg_class (быстрее information_schema).
*/
private function partitionExists(string $name): bool
{
$row = DB::selectOne(
"SELECT 1 AS exists FROM pg_class WHERE relname = ? AND relkind = 'r'",
[$name],
);
return $row !== null;
}
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
/**
* Сброс tenants.delivered_in_month + projects.delivered_in_month = 0.
*
* Spec: docs/superpowers/specs/2026-05-11-plan4-billing-csv-admin-design.md §4.1
* Расписание: 1-го числа каждого месяца в 00:00 Europe/Moscow.
*
* Идёт через connection `pgsql_supplier` (BYPASSRLS-роль crm_supplier_worker)
* паттерн ResetDeliveredTodayCommand. Один statement на таблицу, без SET LOCAL.
*/
class ResetMonthlyCountersCommand extends Command
{
protected $signature = 'projects:reset-monthly';
protected $description = 'Сброс tenants.delivered_in_month + projects.delivered_in_month = 0 (1-го числа в 00:00 МСК, Plan 4)';
public function handle(): int
{
// Без оборачивающего DB::transaction: два UPDATE'а независимо идемпотентны
// (WHERE delivered_in_month <> 0). Если один упадёт, следующий cron-запуск
// довершит сброс. Атомарность не требуется. Дополнительно — оборачивание
// ломает тесты с shared PDO (SharesSupplierPdo trait): PostgreSQL не
// допускает nested transactions без savepoints, см. precedent
// ResetDeliveredTodayCommand (тоже без обёртки).
$tenants = DB::connection('pgsql_supplier')
->update('UPDATE tenants SET delivered_in_month = 0 WHERE delivered_in_month <> 0');
$projects = DB::connection('pgsql_supplier')
->update('UPDATE projects SET delivered_in_month = 0 WHERE delivered_in_month <> 0');
$this->info("Monthly reset: {$tenants} tenants, {$projects} projects.");
return self::SUCCESS;
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\Exceptions\Billing;
use RuntimeException;
/**
* Выбрасывается LedgerService::chargeForDelivery, когда tenant не имеет
* ни prepaid-лидов (balance_leads >= 1), ни рублей под текущую tier-цену
* (balance_rub * 100 >= priceKopecks).
*
* Ловится в RouteSupplierLeadJob::createDealCopyForProject инициирует
* auto-pause flow (см. spec §4.2).
*/
final class InsufficientBalanceException extends RuntimeException
{
public function __construct(
public readonly int $priceKopecks,
public readonly string $balanceRub,
public readonly int $balanceLeads,
?\Throwable $previous = null,
) {
parent::__construct(
sprintf(
'Insufficient balance: price_kopecks=%d, balance_rub=%s, balance_leads=%d',
$priceKopecks, $balanceRub, $balanceLeads,
),
previous: $previous,
);
}
}
@@ -4,7 +4,10 @@ declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Concerns\ResolvesAdminUserId;
use App\Http\Controllers\Controller;
use App\Models\BalanceTransaction;
use App\Models\SaasAdminAuditLog;
use Carbon\CarbonImmutable;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -19,6 +22,183 @@ use Illuminate\Support\Facades\DB;
*/
class AdminBillingController extends Controller
{
use ResolvesAdminUserId;
/** GET /api/admin/billing/tariff-plans — список планов для диалога смены тарифа. */
public function tariffPlans(): JsonResponse
{
$plans = DB::table('tariff_plans')
->select(['id', 'name', 'price_monthly'])
->orderBy('price_monthly')
->get()
->map(fn ($p) => [
'id' => (int) $p->id,
'name' => $p->name,
'price_monthly' => (string) $p->price_monthly,
]);
return response()->json(['plans' => $plans]);
}
/** PATCH /api/admin/billing/tenants/{id}/status — приостановить/разблокировать тенанта. */
public function updateStatus(Request $request, int $id): JsonResponse
{
$validated = $request->validate([
'status' => ['required', 'in:active,suspended'],
'reason' => ['required', 'string', 'min:10', 'max:1000'],
]);
$tenant = $this->findActiveTenant($id);
$adminUserId = $this->resolveAdminUserId($request, 'system-billing@liderra.local', 'System Billing Bot');
DB::transaction(function () use ($tenant, $validated, $adminUserId, $request): void {
// tenants / saas_admin_audit_log не под RLS — SET LOCAL не нужен (ср. refund()).
DB::table('tenants')->where('id', $tenant->id)->update([
'status' => $validated['status'],
'updated_at' => now(),
]);
SaasAdminAuditLog::create([
'admin_user_id' => $adminUserId,
'action' => $validated['status'] === 'suspended' ? 'tenant.suspend' : 'tenant.activate',
'target_type' => 'tenant',
'target_id' => $tenant->id,
'target_tenant_id' => $tenant->id,
'payload_before' => ['status' => $tenant->status],
'payload_after' => ['status' => $validated['status']],
'reason' => $validated['reason'],
'ip_address' => $request->ip() ?? '127.0.0.1',
'user_agent' => $request->userAgent(),
]);
});
return response()->json(['id' => $tenant->id, 'status' => $validated['status']]);
}
/** POST /api/admin/billing/tenants/{id}/refund — возврат средств: списание с баланса + ledger-запись. */
public function refund(Request $request, int $id): JsonResponse
{
$validated = $request->validate([
'amount_rub' => ['required', 'numeric', 'gt:0'],
'reason' => ['required', 'string', 'min:10', 'max:1000'],
]);
$this->findActiveTenant($id); // ранний 404; авторитетный баланс перечитывается под локом ниже
$amount = number_format((float) $validated['amount_rub'], 2, '.', '');
$adminUserId = $this->resolveAdminUserId($request, 'system-billing@liderra.local', 'System Billing Bot');
/** @var array{transaction_id:int, balance_rub:string} $result */
$result = DB::transaction(function () use ($id, $amount, $validated, $adminUserId, $request): array {
DB::statement('SET LOCAL app.current_tenant_id = '.$id);
// Баланс — money-колонка: перечитываем под row-lock внутри транзакции,
// защита от lost-update (конвенция LedgerService — lockForUpdate на tenants).
$tenant = DB::table('tenants')->where('id', $id)->whereNull('deleted_at')
->lockForUpdate()->first();
if ($tenant === null) {
abort(404, 'tenant not found');
}
$balance = (string) $tenant->balance_rub;
if (bccomp($amount, $balance, 2) === 1) {
abort(422, 'refund amount exceeds tenant balance');
}
$newBalance = bcsub($balance, $amount, 2);
DB::table('tenants')->where('id', $id)->update([
'balance_rub' => $newBalance,
'updated_at' => now(),
]);
$tx = BalanceTransaction::create([
'tenant_id' => $id,
'type' => BalanceTransaction::TYPE_REFUND,
'amount_rub' => '-'.$amount,
'amount_leads' => 0,
'balance_rub_after' => $newBalance,
'description' => $validated['reason'],
'admin_user_id' => $adminUserId,
'created_at' => now(),
]);
SaasAdminAuditLog::create([
'admin_user_id' => $adminUserId,
'action' => 'tenant.refund',
'target_type' => 'tenant',
'target_id' => $id,
'target_tenant_id' => $id,
'payload_before' => ['balance_rub' => $balance],
'payload_after' => ['balance_rub' => $newBalance, 'amount_rub' => $amount, 'transaction_id' => $tx->id],
'reason' => $validated['reason'],
'ip_address' => $request->ip() ?? '127.0.0.1',
'user_agent' => $request->userAgent(),
]);
return ['transaction_id' => (int) $tx->id, 'balance_rub' => $newBalance];
});
return response()->json([
'id' => $id,
'balance_rub' => $result['balance_rub'],
'transaction_id' => $result['transaction_id'],
]);
}
/** PATCH /api/admin/billing/tenants/{id}/tariff — сменить тарифный план тенанта. */
public function changeTariff(Request $request, int $id): JsonResponse
{
$validated = $request->validate([
'tariff_id' => ['required', 'integer', 'exists:tariff_plans,id'],
'reason' => ['required', 'string', 'min:10', 'max:1000'],
]);
$tenant = $this->findActiveTenant($id);
$tariff = DB::table('tariff_plans')->where('id', $validated['tariff_id'])->first();
$adminUserId = $this->resolveAdminUserId($request, 'system-billing@liderra.local', 'System Billing Bot');
DB::transaction(function () use ($tenant, $tariff, $validated, $adminUserId, $request): void {
// tenants / saas_admin_audit_log не под RLS — SET LOCAL не нужен (ср. refund()).
DB::table('tenants')->where('id', $tenant->id)->update([
'current_tariff_id' => $tariff->id,
'updated_at' => now(),
]);
SaasAdminAuditLog::create([
'admin_user_id' => $adminUserId,
'action' => 'tenant.change_tariff',
'target_type' => 'tenant',
'target_id' => $tenant->id,
'target_tenant_id' => $tenant->id,
'payload_before' => ['current_tariff_id' => $tenant->current_tariff_id],
'payload_after' => ['current_tariff_id' => (int) $tariff->id],
'reason' => $validated['reason'],
'ip_address' => $request->ip() ?? '127.0.0.1',
'user_agent' => $request->userAgent(),
]);
});
return response()->json([
'id' => $tenant->id,
'tariff_id' => (int) $tariff->id,
'tariff_name' => $tariff->name,
]);
}
/**
* Возвращает не-удалённого тенанта либо abort(404).
*
* @return object{id:int,status:string,balance_rub:string,current_tariff_id:int|null}
*/
private function findActiveTenant(int $id): object
{
$tenant = DB::table('tenants')->where('id', $id)->whereNull('deleted_at')->first();
if ($tenant === null) {
abort(404, 'tenant not found');
}
return $tenant;
}
/** GET /api/admin/billing?search= */
public function index(Request $request): JsonResponse
{
@@ -4,7 +4,9 @@ declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Concerns\ResolvesAdminUserId;
use App\Http\Controllers\Controller;
use App\Models\SaasAdminAuditLog;
use Carbon\CarbonImmutable;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -21,6 +23,8 @@ use Illuminate\Support\Facades\DB;
*/
class AdminIncidentsController extends Controller
{
use ResolvesAdminUserId;
/** GET /api/admin/incidents?type=&severity=&unresolved_only=&limit=&offset= */
public function index(Request $request): JsonResponse
{
@@ -83,6 +87,116 @@ class AdminIncidentsController extends Controller
]);
}
/** POST /api/admin/incidents/{id}/rkn-notify — зафиксировать уведомление РКН (G6, 152-ФЗ). */
public function notifyRkn(Request $request, int $id): JsonResponse
{
$row = DB::table('incidents_log')->where('id', $id)->first();
if ($row === null) {
abort(404, 'incident not found');
}
if ($row->type !== 'data_breach') {
abort(422, 'РКН-уведомление применимо только к инцидентам типа data_breach');
}
if ($row->rkn_notified_at !== null) {
abort(409, 'РКН уже уведомлён по этому инциденту');
}
$adminUserId = $this->resolveAdminUserId($request, 'system-incidents@liderra.local', 'System Incidents Bot');
DB::transaction(function () use ($row, $adminUserId, $request): void {
DB::table('incidents_log')->where('id', $row->id)->update([
'rkn_notified_at' => now(),
'updated_at' => now(),
]);
SaasAdminAuditLog::create([
'admin_user_id' => $adminUserId,
'action' => 'incident.rkn_notify',
'target_type' => 'incident',
'target_id' => $row->id,
'payload_before' => ['rkn_notified_at' => null],
'payload_after' => ['rkn_notified_at' => now()->toIso8601String()],
'reason' => 'Роскомнадзор уведомлён об утечке ПДн через админ-интерфейс (152-ФЗ).',
'ip_address' => $request->ip() ?? '127.0.0.1',
'user_agent' => $request->userAgent(),
]);
});
return $this->show($id);
}
/** GET /api/admin/incidents/{id} — полная карточка инцидента (drill-down G5). */
public function show(int $id): JsonResponse
{
$row = DB::table('incidents_log')->where('id', $id)->first();
if ($row === null) {
abort(404, 'incident not found');
}
$tenantIds = is_array($row->affected_tenant_ids)
? $row->affected_tenant_ids
: ($row->affected_tenant_ids !== null ? $this->parsePgArrayValues((string) $row->affected_tenant_ids) : []);
$tenants = $tenantIds === []
? collect()
: DB::table('tenants')->whereIn('id', $tenantIds)
->select(['id', 'organization_name'])->get();
$admins = DB::table('saas_admin_users')
->whereIn('id', array_filter([$row->created_by_admin_id, $row->closed_by_admin_id]))
->pluck('full_name', 'id');
return response()->json([
'incident' => [
'id' => (int) $row->id,
'incident_id' => $this->formatIncidentId($row),
'type' => $row->type,
'severity' => $row->severity,
'summary' => $row->summary,
'root_cause' => $row->root_cause,
'postmortem_url' => $row->postmortem_url,
'started_at' => CarbonImmutable::parse($row->started_at)->toIso8601String(),
'detected_at' => CarbonImmutable::parse($row->detected_at)->toIso8601String(),
'resolved_at' => $row->resolved_at !== null
? CarbonImmutable::parse($row->resolved_at)->toIso8601String() : null,
'status' => $this->deriveStatus($row),
'affected_tenants' => $tenants->map(fn ($t) => [
'id' => (int) $t->id,
'organization_name' => $t->organization_name,
])->values(),
'affected_users_count' => $row->affected_users_count !== null ? (int) $row->affected_users_count : null,
'notification_sent_at' => $row->notification_sent_at !== null
? CarbonImmutable::parse($row->notification_sent_at)->toIso8601String() : null,
'rkn_notified' => $row->rkn_notified_at !== null,
'rkn_notified_at' => $row->rkn_notified_at !== null
? CarbonImmutable::parse($row->rkn_notified_at)->toIso8601String() : null,
'rkn_deadline_at' => $row->type === 'data_breach' && $row->rkn_notified_at === null
? CarbonImmutable::parse($row->detected_at)->addHours(24)->toIso8601String() : null,
'created_by_admin' => $admins->get($row->created_by_admin_id),
'closed_by_admin' => $row->closed_by_admin_id !== null ? $admins->get($row->closed_by_admin_id) : null,
'created_at' => $row->created_at !== null
? CarbonImmutable::parse($row->created_at)->toIso8601String() : null,
'updated_at' => $row->updated_at !== null
? CarbonImmutable::parse($row->updated_at)->toIso8601String() : null,
],
]);
}
/**
* PG-array literal '{1,2,3}' массив int.
*
* @return list<int>
*/
private function parsePgArrayValues(string $literal): array
{
$trimmed = trim($literal, '{}');
if ($trimmed === '') {
return [];
}
return array_map('intval', explode(',', $trimmed));
}
/** Уникальный человеко-читаемый ID: INC-YYYY-MMDD-NNNN, NNNN = id padded. */
private function formatIncidentId(object $row): string
{
@@ -0,0 +1,191 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\PricingTier;
use App\Models\SaasAdminAuditLog;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
/**
* SaaS-admin CRUD для pricing_tiers (7-ступенчатый тариф).
*
* Spec: docs/superpowers/specs/2026-05-11-plan4-billing-csv-admin-design.md §6.1.
* Без auth-middleware на MVP (паритет с другими /api/admin/*; gated на Б-1).
* Audit trail в saas_admin_audit_log (action='pricing_tiers.create_scheduled' /
* 'pricing_tiers.delete_scheduled').
*
* Конструкция audit-log:
* - schema.saas_admin_audit_log требует NOT NULL admin_user_id и ip_address;
* на MVP admin_user_id берётся из request param (как в
* AdminSystemSettingsController), либо создаётся системный стаб
* `system-pricing@liderra.local`. ip_address $request->ip() или '127.0.0.1'.
* - payload_after хранит {effective_from, tiers} для воспроизводимости.
*/
final class AdminPricingTiersController extends Controller
{
/** GET /api/admin/pricing-tiers */
public function index(): JsonResponse
{
$today = Carbon::now('Europe/Moscow')->toDateString();
$active = PricingTier::query()->where('is_active', true)
->where('effective_from', '<=', $today)
->orderBy('tier_no')->orderBy('effective_from', 'desc')
->get()
->groupBy('tier_no')
->map(fn ($g) => $g->first())
->values();
$scheduled = PricingTier::query()->where('is_active', true)
->where('effective_from', '>', $today)
->orderBy('effective_from')->orderBy('tier_no')
->get()
->groupBy(fn (PricingTier $t) => $t->effective_from->toDateString());
return response()->json([
'data' => [
'active' => $active,
'scheduled' => $scheduled,
],
]);
}
/** POST /api/admin/pricing-tiers */
public function store(Request $request): JsonResponse
{
$todayMsk = Carbon::now('Europe/Moscow')->toDateString();
$request->validate([
'tiers' => ['required', 'array', 'size:7'],
'tiers.*.tier_no' => ['required', 'integer', 'between:1,7'],
'tiers.*.leads_in_tier' => ['nullable', 'integer', 'min:1'],
'tiers.*.price_rub' => ['required', 'numeric', 'min:0'],
'effective_from' => ['sometimes', 'date_format:Y-m-d', 'after:'.$todayMsk],
]);
/** @var array<int, array{tier_no:int, leads_in_tier:?int, price_rub:string|float}> $tiers */
$tiers = $request->input('tiers');
$tierNos = array_column($tiers, 'tier_no');
if (count(array_unique($tierNos)) !== 7) {
abort(422, 'tier_no must be unique 1..7');
}
if (array_diff([1, 2, 3, 4, 5, 6, 7], $tierNos) !== []) {
abort(422, 'all 7 tier_no values required');
}
$tier7 = collect($tiers)->firstWhere('tier_no', 7);
if ($tier7['leads_in_tier'] !== null) {
abort(422, 'tier_no=7 leads_in_tier must be null');
}
foreach ($tiers as $tier) {
if ($tier['tier_no'] !== 7 && ($tier['leads_in_tier'] === null || $tier['leads_in_tier'] < 1)) {
abort(422, "tier_no={$tier['tier_no']} leads_in_tier must be >= 1");
}
}
$effectiveFrom = $request->input('effective_from')
?? Carbon::now('Europe/Moscow')->startOfMonth()->addMonth()->toDateString();
$adminUserId = $this->resolveAdminUserId($request);
DB::transaction(function () use ($tiers, $effectiveFrom, $adminUserId, $request): void {
foreach ($tiers as $tier) {
PricingTier::create([
'tier_no' => $tier['tier_no'],
'leads_in_tier' => $tier['leads_in_tier'],
'price_per_lead_kopecks' => (int) round(((float) $tier['price_rub']) * 100),
'is_active' => true,
'effective_from' => $effectiveFrom,
]);
}
SaasAdminAuditLog::create([
'admin_user_id' => $adminUserId,
'action' => 'pricing_tiers.create_scheduled',
'target_type' => 'pricing_tiers',
'target_id' => null,
'payload_before' => null,
'payload_after' => ['effective_from' => $effectiveFrom, 'tiers' => $tiers],
'reason' => 'Scheduled pricing-tier update via admin UI.',
'ip_address' => $request->ip() ?? '127.0.0.1',
'user_agent' => $request->userAgent(),
]);
});
return response()->json(['effective_from' => $effectiveFrom], Response::HTTP_CREATED);
}
/** DELETE /api/admin/pricing-tiers/scheduled/{effective_from} */
public function deleteScheduled(Request $request, string $effectiveFrom): JsonResponse
{
if (! preg_match('/^\d{4}-\d{2}-\d{2}$/', $effectiveFrom)) {
abort(400, 'invalid date format');
}
$todayMsk = Carbon::now('Europe/Moscow')->toDateString();
if ($effectiveFrom <= $todayMsk) {
abort(409, 'cannot delete past or active set');
}
$adminUserId = $this->resolveAdminUserId($request);
DB::transaction(function () use ($effectiveFrom, $adminUserId, $request): void {
$deleted = PricingTier::where('effective_from', $effectiveFrom)->delete();
SaasAdminAuditLog::create([
'admin_user_id' => $adminUserId,
'action' => 'pricing_tiers.delete_scheduled',
'target_type' => 'pricing_tiers',
'target_id' => null,
'payload_before' => ['effective_from' => $effectiveFrom],
'payload_after' => ['rows_deleted' => $deleted],
'reason' => 'Cancelled scheduled pricing-tier set via admin UI.',
'ip_address' => $request->ip() ?? '127.0.0.1',
'user_agent' => $request->userAgent(),
]);
});
return response()->json(['ok' => true]);
}
/**
* Резолвит admin_user_id: из request input (на MVP) либо создаёт
* системный стаб-аккаунт `system-pricing@liderra.local`, чтобы соблюсти
* NOT NULL constraint + FK на saas_admin_users.
*/
private function resolveAdminUserId(Request $request): int
{
$requested = $request->input('admin_user_id');
if (is_int($requested) || (is_string($requested) && ctype_digit($requested))) {
$existing = DB::table('saas_admin_users')->where('id', (int) $requested)->value('id');
if ($existing !== null) {
return (int) $existing;
}
}
$existingId = DB::table('saas_admin_users')
->where('email', 'system-pricing@liderra.local')
->value('id');
if ($existingId !== null) {
return (int) $existingId;
}
return (int) DB::table('saas_admin_users')->insertGetId([
'email' => 'system-pricing@liderra.local',
'full_name' => 'System Pricing Bot',
'password_hash' => '$2y$04$system-stub-not-loginable',
'role' => 'super_admin',
'is_active' => false,
'sso_provider' => 'local',
'is_break_glass' => false,
]);
}
}
@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\SaasAdminAuditLog;
use App\Models\Supplier;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
/**
* SaaS-admin CRUD для suppliers (B1/B2/B3 закупочные цены).
*
* Spec: docs/superpowers/specs/2026-05-11-plan4-billing-csv-admin-design.md §6.2.
* Без auth-middleware на MVP (паритет с AdminPricingTiersController; gated на Б-1).
* Audit trail в saas_admin_audit_log (action='suppliers.update').
*
* Reuses pattern from AdminPricingTiersController::resolveAdminUserId(), но с
* системным стабом 'system-supplier@liderra.local' (отдельный actor для
* фильтрации audit-event'ов по этой подсистеме).
*/
final class AdminSuppliersController extends Controller
{
/** GET /api/admin/suppliers */
public function index(): JsonResponse
{
return response()->json([
'data' => Supplier::query()->orderBy('sort_order')->get(),
]);
}
/** PATCH /api/admin/suppliers/{id} */
public function update(Request $request, int $id): JsonResponse
{
$request->validate([
'cost_rub' => ['sometimes', 'numeric', 'min:0'],
'quality_score' => ['sometimes', 'numeric', 'between:0,9.99'],
'is_active' => ['sometimes', 'boolean'],
]);
$supplier = Supplier::findOrFail($id);
/** @var array<string, mixed> $changes */
$changes = $request->only(['cost_rub', 'quality_score', 'is_active']);
DB::transaction(function () use ($supplier, $changes, $request): void {
/** @var array<string, mixed> $before */
$before = $supplier->only(array_keys($changes));
$supplier->update($changes);
SaasAdminAuditLog::create([
'admin_user_id' => $this->resolveAdminUserId($request),
'action' => 'suppliers.update',
'target_type' => 'suppliers',
'target_id' => $supplier->id,
'payload_before' => $before,
'payload_after' => $changes,
'reason' => 'Supplier cost/quality update via admin UI.',
'ip_address' => $request->ip() ?? '127.0.0.1',
'user_agent' => $request->userAgent(),
]);
});
return response()->json(['data' => $supplier->fresh()]);
}
/**
* Резолвит admin_user_id: из request input (на MVP) либо создаёт
* системный стаб-аккаунт `system-supplier@liderra.local`, чтобы соблюсти
* NOT NULL constraint + FK на saas_admin_users.
*
* Идентичная логика AdminPricingTiersController::resolveAdminUserId();
* отличается только email системного стаба (для фильтрации audit-event'ов).
*/
private function resolveAdminUserId(Request $request): int
{
$requested = $request->input('admin_user_id');
if (is_int($requested) || (is_string($requested) && ctype_digit($requested))) {
$existing = DB::table('saas_admin_users')->where('id', (int) $requested)->value('id');
if ($existing !== null) {
return (int) $existing;
}
}
$existingId = DB::table('saas_admin_users')
->where('email', 'system-supplier@liderra.local')
->value('id');
if ($existingId !== null) {
return (int) $existingId;
}
return (int) DB::table('saas_admin_users')->insertGetId([
'email' => 'system-supplier@liderra.local',
'full_name' => 'System Supplier Bot',
'password_hash' => '$2y$04$system-stub-not-loginable',
'role' => 'super_admin',
'is_active' => false,
'sso_provider' => 'local',
'is_break_glass' => false,
]);
}
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\ApiKey;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
/**
* API-ключи тенанта (audit D2/D3/J5). Endpoints под auth:sanctum + tenant.
*
* Полный ключ показывается ОДИН раз в ответе regenerate(). В БД хранится
* только bcrypt key_hash + key_prefix (первые 10 символов для UI). У тенанта
* поддерживается один активный ключ: regenerate деактивирует прежние.
*/
class ApiKeyController extends Controller
{
private const KEY_PREFIX = 'lpkapi_';
public function index(Request $request): JsonResponse
{
$tenantId = (int) $request->user()->tenant_id;
// Defense-in-depth: явный where даже при RLS — в тестах PG superuser BYPASSRLS.
$keys = ApiKey::query()
->where('tenant_id', $tenantId)
->where('is_active', true)
->where('expires_at', '>', now())
->orderByDesc('created_at')
->get(['id', 'name', 'key_prefix', 'last_used_at', 'expires_at', 'created_at']);
return response()->json(['data' => $keys]);
}
public function regenerate(Request $request): JsonResponse
{
$tenantId = (int) $request->user()->tenant_id;
$userId = (int) $request->user()->id;
// Один активный ключ на тенанта — прежние деактивируются.
ApiKey::query()
->where('tenant_id', $tenantId)
->where('is_active', true)
->update(['is_active' => false]);
$plainKey = self::KEY_PREFIX.Str::random(48);
$key = ApiKey::query()->create([
'tenant_id' => $tenantId,
'user_id' => $userId,
'name' => 'API-ключ',
'key_hash' => Hash::make($plainKey),
'key_prefix' => substr($plainKey, 0, 10),
'scopes' => ['read'],
'expires_at' => now()->addYear(),
'is_active' => true,
'created_at' => now(),
]);
return response()->json([
'id' => $key->id,
'name' => $key->name,
'key' => $plainKey,
'key_prefix' => $key->key_prefix,
], Response::HTTP_CREATED);
}
}
@@ -228,6 +228,31 @@ class AuthController extends Controller
]);
}
/**
* PATCH /api/auth/me обновление профиля текущего пользователя
* (имя, фамилия, телефон, тайм-зона). Email менять нельзя (через support).
*
* Audit J6/D1 (ProfileTab). Зеркалит updateNotificationPreferences:
* та же группа auth:sanctum, тот же inline-validate, тот же userResource.
*/
public function updateProfile(Request $request): JsonResponse
{
$validated = $request->validate([
'first_name' => ['required', 'string', 'max:255'],
'last_name' => ['required', 'string', 'max:255'],
'phone' => ['nullable', 'string', 'max:20'],
'timezone' => ['required', 'timezone'],
]);
/** @var User $user */
$user = $request->user();
$user->update($validated);
return response()->json([
'user' => $this->userResource($user->fresh()),
]);
}
/**
* Ключ throttle для login: email|ip защищает email от брутфорса даже
* за NAT'ом, и IP от перебора емейлов с одного источника.
@@ -333,6 +358,8 @@ class AuthController extends Controller
'email' => $user->email,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'phone' => $user->phone,
'timezone' => $user->timezone,
'tenant_id' => $user->tenant_id,
'totp_enabled' => $user->totp_enabled,
'last_login_at' => $user->last_login_at,
@@ -0,0 +1,186 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\BalanceTransaction;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Billing\BillingTopupService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
/**
* Биллинг тенанта кошелёк, транзакции, счета, пополнение (audit E1/E3).
*
* Все эндпоинты под middleware [auth:sanctum, tenant] (RLS-контекст).
* Отдельно от TenantChargesController (lead_charges ledger) и
* AdminBillingController (SaaS-уровневые агрегаты).
*
* E1: POST /api/billing/topup MVP-stub пополнения (без платёжного шлюза).
* E3: GET wallet/transactions/invoices данные для BillingView Overview.
*/
class BillingController extends Controller
{
public function __construct(
private readonly BillingTopupService $topupService,
) {}
/**
* POST /api/billing/topup пополнить рублёвый баланс.
*
* MVP-stub: кредитует баланс немедленно (без ЮKassa реальная оплата
* post-Б-1). Записывает append-only строку balance_transactions(topup).
*/
public function topup(Request $request): JsonResponse
{
$validated = $request->validate([
'amount_rub' => ['required', 'numeric', 'min:100', 'max:1000000', 'decimal:0,2'],
]);
/** @var User $user */
$user = $request->user();
// Нормализуем в DECIMAL-строку scale 2 для bcmath (НЕ float).
$amountRub = bcadd((string) $validated['amount_rub'], '0', 2);
$tx = $this->topupService->topup((int) $user->tenant_id, $amountRub, (int) $user->id);
return response()->json([
'transaction' => [
'id' => $tx->id,
'type' => $tx->type,
'amount_rub' => $tx->amount_rub,
'balance_rub_after' => $tx->balance_rub_after,
'created_at' => $tx->created_at,
],
'balance_rub' => $tx->balance_rub_after,
], 201);
}
/**
* GET /api/billing/wallet балансы тенанта + текущий тариф + runway.
*/
public function wallet(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
/** @var Tenant $tenant */
$tenant = Tenant::query()->with('tariff')->findOrFail((int) $user->tenant_id);
return response()->json([
'balance_rub' => $tenant->balance_rub,
'balance_leads' => $tenant->balance_leads,
'runway_days' => $this->runwayDays($tenant),
'tariff' => $tenant->tariff === null ? null : [
'code' => $tenant->tariff->code,
'name' => $tenant->tariff->name,
'price_monthly' => $tenant->tariff->price_monthly,
'billing_model' => $tenant->tariff->billing_model,
'features' => $tenant->tariff->features ?? [],
],
]);
}
/**
* GET /api/billing/transactions?type=topup|lead_charge|refund&page=N
* пагинированная история balance_transactions тенанта (20/страница).
*/
public function transactions(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$tenantId = (int) $user->tenant_id;
// Явный tenant_id фильтр — defense-in-depth поверх RLS (тесты идут
// под superuser BYPASSRLS; паттерн TenantChargesController).
$query = BalanceTransaction::query()
->where('tenant_id', $tenantId)
->orderBy('created_at', 'desc')
->orderBy('id', 'desc');
$type = $request->query('type');
if (is_string($type) && in_array($type, ['topup', 'lead_charge', 'refund'], true)) {
$query->where('type', $type);
}
$page = $query->paginate(20);
return response()->json([
'data' => array_map(static fn (BalanceTransaction $tx): array => [
'id' => $tx->id,
'code' => 'TX-'.$tx->id,
'type' => $tx->type,
'description' => $tx->description,
'amount_rub' => $tx->amount_rub,
'amount_leads' => $tx->amount_leads,
'balance_rub_after' => $tx->balance_rub_after,
'created_at' => $tx->created_at,
], $page->items()),
'meta' => [
'current_page' => $page->currentPage(),
'last_page' => $page->lastPage(),
'total' => $page->total(),
'per_page' => $page->perPage(),
],
]);
}
/**
* GET /api/billing/invoices счета тенанта (saas_invoices).
*
* Real-but-empty на MVP: saas_invoices.legal_entity_id NOT NULL требует
* зарегистрированного юр-лица (блокируется Б-1). Read-only выборка через
* DB::table без Eloquent-модели (паттерн AdminBillingController).
*/
public function invoices(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$tenantId = (int) $user->tenant_id;
$rows = DB::table('saas_invoices')
->where('tenant_id', $tenantId)
->orderBy('issued_at', 'desc')
->get(['id', 'invoice_number', 'amount_total', 'status', 'issued_at', 'pdf_path']);
return response()->json([
'data' => $rows->map(static fn (\stdClass $r): array => [
'id' => $r->id,
'invoice_number' => $r->invoice_number,
'amount_total' => $r->amount_total,
'status' => $r->status,
'issued_at' => $r->issued_at,
'has_pdf' => $r->pdf_path !== null,
])->all(),
]);
}
/**
* Прогноз «на сколько дней хватит баланса» оценочный UX-показатель.
*
* = balance_rub / (рублёвые списания за 30 дней / 30). NULL, если списаний
* не было. Float здесь допустим: грубая оценка для шапки, НЕ мутация
* баланса (мутации баланса строго bcmath, см. BillingTopupService).
* Отрицательный баланс 0 (тенант уже в минусе, runway не может быть < 0).
*/
private function runwayDays(Tenant $tenant): ?int
{
$spent = abs((float) DB::table('balance_transactions')
->where('tenant_id', $tenant->id)
->where('type', BalanceTransaction::TYPE_LEAD_CHARGE)
->where('created_at', '>=', now()->subDays(30))
->sum('amount_rub'));
if ($spent <= 0.0) {
return null;
}
$perDay = $spent / 30.0;
return max(0, (int) floor((float) $tenant->balance_rub / $perDay));
}
}
@@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Tenant;
use Carbon\CarbonImmutable;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
/**
* Дашборд агрегат для DashboardView (audit C1/J3).
*
* GET /api/dashboard/summary?tenant_id={id}&range=today|7d|30d
*
* На MVP без auth-middleware (tenant_id параметром, как DealController).
* Production: middleware('auth:sanctum','tenant') tenant_id из user.
*
* Все агрегаты tenant-scoped, deleted_at IS NULL, is_test=false.
* RLS-обёртка SET LOCAL app.current_tenant_id (PgBouncer-safe), как DealController.
*/
class DashboardController extends Controller
{
private const RU_WEEKDAYS = ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'];
public function summary(Request $request): JsonResponse
{
$tenantId = (int) $request->query('tenant_id', '0');
if ($tenantId < 1) {
return response()->json(['message' => 'Параметр tenant_id обязателен.'], 422);
}
$tenant = Tenant::find($tenantId);
if ($tenant === null) {
return response()->json(['message' => 'Тенант не найден.'], 404);
}
$range = in_array($request->query('range'), ['today', '7d', '30d'], true)
? (string) $request->query('range')
: '7d';
// MSK: activity-бакеты и range-границы должны совпадать с SQL
// `AT TIME ZONE 'Europe/Moscow'`. config('app.timezone') = UTC.
$now = CarbonImmutable::now('Europe/Moscow');
[$windowStart, $prevStart] = match ($range) {
'today' => [$now->startOfDay(), $now->startOfDay()->subDay()],
'30d' => [$now->subDays(30), $now->subDays(60)],
default => [$now->subDays(7), $now->subDays(14)],
};
$data = DB::transaction(function () use ($tenantId, $tenant, $now, $range, $windowStart, $prevStart) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
$base = fn () => DB::table('deals')
->where('tenant_id', $tenantId)
->whereNull('deleted_at')
->where('is_test', false);
// --- leads received: текущее + предыдущее окно ---
$curLeads = (clone $base())->whereBetween('received_at', [$windowStart, $now])->count();
$prevLeads = (clone $base())->whereBetween('received_at', [$prevStart, $windowStart])->count();
// --- conversion: % статуса 'paid' в окне ---
$curPaid = (clone $base())->where('status', 'paid')
->whereBetween('received_at', [$windowStart, $now])->count();
$prevPaid = (clone $base())->where('status', 'paid')
->whereBetween('received_at', [$prevStart, $windowStart])->count();
$curConv = $curLeads > 0 ? round($curPaid / $curLeads * 100, 1) : 0.0;
$prevConv = $prevLeads > 0 ? round($prevPaid / $prevLeads * 100, 1) : 0.0;
// --- active projects ---
$activeProjects = DB::table('projects')
->where('tenant_id', $tenantId)
->whereNull('archived_at')
->where('is_active', true)
->count();
$maxProjects = (int) (($tenant->limits['max_projects'] ?? 0));
// --- activity: 7 daily-бакетов по received_at (MSK) ---
$activityStart = $now->subDays(6)->startOfDay();
$byDay = (clone $base())
->where('received_at', '>=', $activityStart)
->selectRaw("to_char((received_at AT TIME ZONE 'Europe/Moscow')::date, 'YYYY-MM-DD') AS d, COUNT(*) AS c")
->groupBy('d')
->pluck('c', 'd');
$points = [];
$labels = [];
for ($i = 6; $i >= 0; $i--) {
$day = $now->subDays($i);
$key = $day->format('Y-m-d');
$points[] = (int) ($byDay[$key] ?? 0);
$labels[] = $i === 0 ? 'сегодня' : self::RU_WEEKDAYS[(int) $day->format('w')];
}
$maxPoint = max(0, ...$points);
$axisMax = max(10, (int) (ceil($maxPoint / 10) * 10));
// --- funnel: текущий снимок по статусам ---
$funnel = (clone $base())
->selectRaw('status, COUNT(*) AS c')
->groupBy('status')
->pluck('c', 'status')
->map(fn ($c) => (int) $c)
->toArray();
// --- runway ---
// runway опирается на приток за фиксированное 7-дневное окно,
// независимо от выбранного range (для today/30d $curLeads — не 7-дневный).
$leads7d = (clone $base())->whereBetween('received_at', [$now->subDays(7), $now])->count();
$avgDaily = $leads7d / 7.0;
$balanceLeads = (int) ($tenant->balance_leads ?? 0);
$runwayDays = $avgDaily > 0 ? (int) floor($balanceLeads / $avgDaily) : 0;
return [
'range' => $range,
'leads_received' => self::deltaBlock($curLeads, $prevLeads, 'delta_pct', self::pctDelta($curLeads, $prevLeads)),
'conversion' => self::deltaBlock($curConv, $prevConv, 'delta_pp', round($curConv - $prevConv, 1)),
'active_projects' => ['active' => $activeProjects, 'limit' => $maxProjects],
'balance' => [
'amount_rub' => (string) $tenant->balance_rub,
'runway_days' => $runwayDays,
'runway_leads' => $balanceLeads,
],
'activity' => ['points' => $points, 'labels' => $labels, 'max' => $axisMax],
'funnel' => (object) $funnel,
];
});
return response()->json($data);
}
/** Процентная дельта current vs previous; 0.0 если previous=0. */
private static function pctDelta(float $cur, float $prev): float
{
return $prev > 0 ? round(($cur - $prev) / $prev * 100, 1) : 0.0;
}
/** Блок {value, <deltaKey>, delta_dir}. */
private static function deltaBlock(float $value, float $prev, string $deltaKey, float $delta): array
{
$dir = $value > $prev ? 'up' : ($value < $prev ? 'down' : 'neutral');
return ['value' => $value, $deltaKey => $delta, 'delta_dir' => $dir];
}
}
@@ -7,7 +7,6 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\ActivityLog;
use App\Models\Deal;
use App\Models\Tenant;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@@ -20,6 +19,8 @@ use Illuminate\Support\Facades\DB;
* bulk + export + helpers). Этот класс отвечает только за многоразовые
* массовые операции; single-resource действия остаются в DealController.
*
* J1 (Sprint 3F): auth:sanctum+tenant, tenant_id из auth()->user().
*
* O-perf-01: N+1 устранён.
*
* transition: сначала SELECT всех сделок tenant'а из ids, чтобы отфильтровать
@@ -41,23 +42,19 @@ class DealBulkActionController extends Controller
/**
* POST /api/deals/transition bulk status-update.
*
* Body: {tenant_id, ids: [int...], status: slug}.
* Body: {ids: [int...], status: slug}.
* Response: {updated, requested, status} (updated = реально изменённых,
* без NO-OP).
*/
public function transition(Request $request): JsonResponse
{
$validated = $request->validate([
'tenant_id' => 'required|integer|min:1',
'ids' => 'required|array|min:1|max:1000',
'ids.*' => 'integer|min:1',
'status' => 'required|string|max:50',
]);
$tenant = Tenant::find($validated['tenant_id']);
if ($tenant === null) {
return response()->json(['message' => 'Тенант не найден.'], 404);
}
$tenantId = (int) $request->user()->tenant_id;
$statusExists = DB::table('lead_statuses')->where('slug', $validated['status'])->exists();
if (! $statusExists) {
@@ -67,14 +64,14 @@ class DealBulkActionController extends Controller
], 422);
}
$updated = DB::transaction(function () use ($validated, $tenant) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenant->id);
$updated = DB::transaction(function () use ($validated, $tenantId) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
// Фаза 1: SELECT — нужны id и предыдущий status для каждой строки,
// чтобы (а) отфильтровать NO-OP и (б) сохранить prev в context.from.
// Defense-in-depth where(tenant_id) — защита от кросс-tenant id.
$rows = Deal::query()
->where('tenant_id', $tenant->id)
->where('tenant_id', $tenantId)
->whereIn('id', $validated['ids'])
->get(['id', 'status']);
@@ -88,7 +85,7 @@ class DealBulkActionController extends Controller
// Фаза 2: bulk-UPDATE 1 запросом вместо N.
Deal::query()
->where('tenant_id', $tenant->id)
->where('tenant_id', $tenantId)
->whereIn('id', $changedIds)
->update([
'status' => $validated['status'],
@@ -100,7 +97,7 @@ class DealBulkActionController extends Controller
// массив сериализуем в JSON руками, остальные scalar-поля передаём
// напрямую. Триггер audit_chain_hash() заполнит log_hash на уровне БД.
$logRows = $changed->map(fn (Deal $d) => [
'tenant_id' => $tenant->id,
'tenant_id' => $tenantId,
'user_id' => null,
'deal_id' => $d->id,
'event' => ActivityLog::EVENT_DEAL_STATUS_CHANGED,
@@ -127,7 +124,7 @@ class DealBulkActionController extends Controller
/**
* DELETE /api/deals bulk soft-delete.
*
* Body: {tenant_id, ids: [int...]}.
* Body: {ids: [int...]}.
* Response: {deleted, requested}.
*
* Soft-delete сохраняется (см. документацию в DealController.destroy на
@@ -137,23 +134,19 @@ class DealBulkActionController extends Controller
public function destroy(Request $request): JsonResponse
{
$validated = $request->validate([
'tenant_id' => 'required|integer|min:1',
'ids' => 'required|array|min:1|max:1000',
'ids.*' => 'integer|min:1',
]);
$tenant = Tenant::find($validated['tenant_id']);
if ($tenant === null) {
return response()->json(['message' => 'Тенант не найден.'], 404);
}
$tenantId = (int) $request->user()->tenant_id;
$deleted = DB::transaction(function () use ($validated, $tenant) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenant->id);
$deleted = DB::transaction(function () use ($validated, $tenantId) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
// SELECT id'шников живых сделок tenant'а из ids — для bulk-INSERT
// в activity_log по списку реально удаляемых (NO-OP idempotency).
$targetIds = Deal::query()
->where('tenant_id', $tenant->id)
->where('tenant_id', $tenantId)
->whereIn('id', $validated['ids'])
->whereNull('deleted_at')
->pluck('id')
@@ -166,7 +159,7 @@ class DealBulkActionController extends Controller
$now = now();
Deal::query()
->where('tenant_id', $tenant->id)
->where('tenant_id', $tenantId)
->whereIn('id', $targetIds)
->whereNull('deleted_at')
->update([
@@ -175,7 +168,7 @@ class DealBulkActionController extends Controller
]);
$logRows = array_map(fn (int $id) => [
'tenant_id' => $tenant->id,
'tenant_id' => $tenantId,
'user_id' => null,
'deal_id' => $id,
'event' => ActivityLog::EVENT_DEAL_DELETED,
@@ -197,30 +190,26 @@ class DealBulkActionController extends Controller
/**
* POST /api/deals/restore bulk restore soft-deleted.
*
* Body: {tenant_id, ids: [int...]}.
* Body: {ids: [int...]}.
* Response: {restored, requested}.
*/
public function restore(Request $request): JsonResponse
{
$validated = $request->validate([
'tenant_id' => 'required|integer|min:1',
'ids' => 'required|array|min:1|max:1000',
'ids.*' => 'integer|min:1',
]);
$tenant = Tenant::find($validated['tenant_id']);
if ($tenant === null) {
return response()->json(['message' => 'Тенант не найден.'], 404);
}
$tenantId = (int) $request->user()->tenant_id;
$restored = DB::transaction(function () use ($validated, $tenant) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenant->id);
$restored = DB::transaction(function () use ($validated, $tenantId) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
// withTrashed обходит SoftDeletes global scope; whereNotNull —
// NO-OP idempotency для уже живых.
$targetIds = Deal::query()
->withTrashed()
->where('tenant_id', $tenant->id)
->where('tenant_id', $tenantId)
->whereIn('id', $validated['ids'])
->whereNotNull('deleted_at')
->pluck('id')
@@ -234,7 +223,7 @@ class DealBulkActionController extends Controller
Deal::query()
->withTrashed()
->where('tenant_id', $tenant->id)
->where('tenant_id', $tenantId)
->whereIn('id', $targetIds)
->whereNotNull('deleted_at')
->update([
@@ -243,7 +232,7 @@ class DealBulkActionController extends Controller
]);
$logRows = array_map(fn (int $id) => [
'tenant_id' => $tenant->id,
'tenant_id' => $tenantId,
'user_id' => null,
'deal_id' => $id,
'event' => ActivityLog::EVENT_DEAL_RESTORED,
+33 -51
View File
@@ -9,7 +9,6 @@ use App\Models\ActivityLog;
use App\Models\Deal;
use App\Models\Project;
use App\Models\SupplierLeadCost;
use App\Models\Tenant;
use App\Models\User;
use App\Services\SupplierResolver;
use Illuminate\Http\JsonResponse;
@@ -27,9 +26,7 @@ use Illuminate\Support\Facades\DB;
* `WebhookReceiveController` + `ProcessWebhookJob` (асинхронно через очередь
* с advisory lock + dedup). Этот controller для ручных action'ов из UI.
*
* На MVP без auth-middleware (multi-tenant контекст резолвится по
* `tenant_id` параметру). Production: middleware('auth:sanctum')+'tenant'
* tenant_id из request()->user()->tenant_id; user ID для manager/audit.
* J1 (Sprint 3F): auth:sanctum+tenant, tenant_id из auth()->user().
*
* Manual-create отличается от webhook'а:
* - source_crm_id = NULL (не из webhook).
@@ -42,7 +39,7 @@ use Illuminate\Support\Facades\DB;
class DealController extends Controller
{
/**
* GET /api/deals?tenant_id={id}&status_in[]=...&project_id=...&manager_id=...&search=...&limit=...&offset=...
* GET /api/deals?status_in[]=...&project_id=...&manager_id=...&search=...&limit=...&offset=...
*
* Список сделок tenant'а с relations (project.name, manager.first/last/email).
* Используется в `DealsView`/`KanbanView` вместо MOCK_DEALS.
@@ -53,20 +50,10 @@ class DealController extends Controller
* (received_at, id)).
*
* RLS: SET LOCAL app.current_tenant_id внутри транзакции (PgBouncer-safe).
* Чужие сделки отфильтрует политика, даже если клиент подсунет чужой
* tenant_id (без auth на MVP, на prod middleware).
*/
public function index(Request $request): JsonResponse
{
$tenantId = (int) $request->query('tenant_id', '0');
if ($tenantId < 1) {
return response()->json(['message' => 'Параметр tenant_id обязателен.'], 422);
}
$tenant = Tenant::find($tenantId);
if ($tenant === null) {
return response()->json(['message' => 'Тенант не найден.'], 404);
}
$tenantId = (int) $request->user()->tenant_id;
$statuses = (array) $request->query('status_in', []);
$projectId = $request->query('project_id') !== null ? (int) $request->query('project_id') : null;
@@ -75,6 +62,7 @@ class DealController extends Controller
$limit = max(1, min(500, (int) $request->query('limit', '100')));
$offset = max(0, (int) $request->query('offset', '0'));
$onlyDeleted = $request->boolean('only_deleted');
$countOnly = $request->boolean('count_only');
$cursorRaw = (string) $request->query('cursor', '');
// Sprint 4 Phase A (audit O-perf-04): keyset pagination через cursor.
@@ -93,7 +81,7 @@ class DealController extends Controller
$cursor = ['r' => (string) $parsed['r'], 'i' => (int) $parsed['i']];
}
[$deals, $total, $nextCursor] = DB::transaction(function () use ($tenantId, $statuses, $projectId, $managerId, $search, $limit, $offset, $onlyDeleted, $cursor) {
[$deals, $total, $nextCursor] = DB::transaction(function () use ($tenantId, $statuses, $projectId, $managerId, $search, $limit, $offset, $onlyDeleted, $cursor, $countOnly) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
// Defense-in-depth: явный where(tenant_id) поверх RLS — на тестах
@@ -128,6 +116,12 @@ class DealController extends Controller
});
}
// Audit B2: count_only — отдаём только COUNT(*), пропуская SELECT строк
// и cursor/offset-логику (лёгкий запрос для бейджа в сайдбаре).
if ($countOnly) {
return [collect(), $query->count(), null];
}
if ($cursor !== null) {
// Keyset: PG row constructor через индекс на (received_at DESC, id DESC).
// Не считаем total (дорого без COUNT(*); клиент при необходимости
@@ -172,6 +166,10 @@ class DealController extends Controller
return [$rows, $total, $next];
});
if ($countOnly) {
return response()->json(['total' => $total]);
}
$payload = [
'deals' => $deals->map(fn (Deal $d) => [
'id' => $d->id,
@@ -203,7 +201,7 @@ class DealController extends Controller
}
/**
* GET /api/deals/{id}?tenant_id={id} детали сделки + recent activity events.
* GET /api/deals/{id} детали сделки + recent activity events.
*
* Используется в DealDetailDrawer (правая панель). Возвращает deal с
* relations + до 50 последних activity_log событий по этой сделке.
@@ -213,15 +211,7 @@ class DealController extends Controller
*/
public function show(Request $request, int $id): JsonResponse
{
$tenantId = (int) $request->query('tenant_id', '0');
if ($tenantId < 1) {
return response()->json(['message' => 'Параметр tenant_id обязателен.'], 422);
}
$tenant = Tenant::find($tenantId);
if ($tenant === null) {
return response()->json(['message' => 'Тенант не найден.'], 404);
}
$tenantId = (int) $request->user()->tenant_id;
[$deal, $events] = DB::transaction(function () use ($tenantId, $id) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
@@ -291,7 +281,7 @@ class DealController extends Controller
/**
* PATCH /api/deals/{id} частичное редактирование сделки из DealDetailDrawer.
*
* Body (все поля optional, должно быть хотя бы одно): {tenant_id, comment?,
* Body (все поля optional, должно быть хотя бы одно): {comment?,
* manager_id?, status?}.
*
* Каждое изменение пишется в ActivityLog с правильным event-type:
@@ -309,16 +299,12 @@ class DealController extends Controller
public function update(Request $request, int $id): JsonResponse
{
$validated = $request->validate([
'tenant_id' => 'required|integer|min:1',
'comment' => 'nullable|string|max:5000',
'manager_id' => 'nullable|integer|min:1',
'status' => 'nullable|string|max:50',
]);
$tenant = Tenant::find($validated['tenant_id']);
if ($tenant === null) {
return response()->json(['message' => 'Тенант не найден.'], 404);
}
$tenantId = (int) $request->user()->tenant_id;
// Validate status slug если передан.
if (array_key_exists('status', $validated) && $validated['status'] !== null) {
@@ -335,7 +321,7 @@ class DealController extends Controller
if (array_key_exists('manager_id', $validated) && $validated['manager_id'] !== null) {
$managerExists = User::query()
->where('id', $validated['manager_id'])
->where('tenant_id', $tenant->id)
->where('tenant_id', $tenantId)
->whereNull('deleted_at')
->where('is_active', true)
->exists();
@@ -347,11 +333,11 @@ class DealController extends Controller
}
}
$deal = DB::transaction(function () use ($validated, $tenant, $id) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenant->id);
$deal = DB::transaction(function () use ($validated, $tenantId, $id) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
$deal = Deal::query()
->where('tenant_id', $tenant->id)
->where('tenant_id', $tenantId)
->where('id', $id)
->first();
@@ -363,7 +349,7 @@ class DealController extends Controller
if (array_key_exists('comment', $validated) && $deal->comment !== $validated['comment']) {
$deal->comment = $validated['comment'];
ActivityLog::create([
'tenant_id' => $tenant->id,
'tenant_id' => $tenantId,
'user_id' => null,
'deal_id' => $deal->id,
'event' => 'deal.commented',
@@ -376,7 +362,7 @@ class DealController extends Controller
$deal->manager_id = $validated['manager_id'];
$deal->assigned_at = $validated['manager_id'] !== null ? now() : null;
ActivityLog::create([
'tenant_id' => $tenant->id,
'tenant_id' => $tenantId,
'user_id' => null,
'deal_id' => $deal->id,
'event' => ActivityLog::EVENT_DEAL_ASSIGNED,
@@ -388,7 +374,7 @@ class DealController extends Controller
$previousStatus = $deal->status;
$deal->status = $validated['status'];
ActivityLog::create([
'tenant_id' => $tenant->id,
'tenant_id' => $tenantId,
'user_id' => null,
'deal_id' => $deal->id,
'event' => ActivityLog::EVENT_DEAL_STATUS_CHANGED,
@@ -425,7 +411,6 @@ class DealController extends Controller
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'tenant_id' => 'required|integer|min:1',
'project_name' => 'required|string|max:255',
'phone' => 'required|string|max:20',
'contact_name' => 'nullable|string|max:255',
@@ -434,17 +419,14 @@ class DealController extends Controller
'comment' => 'nullable|string|max:5000',
]);
$tenant = Tenant::find($validated['tenant_id']);
if ($tenant === null) {
return response()->json(['message' => 'Тенант не найден.'], 404);
}
$tenantId = (int) $request->user()->tenant_id;
// Manager FK guard: если manager_id передан, он должен принадлежать
// этому tenant'у. Иначе можно назначить чужого менеджера на свою сделку.
if (isset($validated['manager_id'])) {
$managerExists = User::query()
->where('id', $validated['manager_id'])
->where('tenant_id', $tenant->id)
->where('tenant_id', $tenantId)
->whereNull('deleted_at')
->where('is_active', true)
->exists();
@@ -459,16 +441,16 @@ class DealController extends Controller
$statusSlug = $validated['status'] ?? 'new';
// Транзакция + RLS: SET LOCAL внутри (PgBouncer-safe).
$deal = DB::transaction(function () use ($validated, $tenant, $statusSlug) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenant->id);
$deal = DB::transaction(function () use ($validated, $tenantId, $statusSlug) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
$project = Project::firstOrCreate(
['tenant_id' => $tenant->id, 'name' => $validated['project_name']],
['tenant_id' => $tenantId, 'name' => $validated['project_name']],
['type' => 'manual'],
);
$deal = Deal::create([
'tenant_id' => $tenant->id,
'tenant_id' => $tenantId,
'source_crm_id' => null, // manual
'project_id' => $project->id,
'phone' => $validated['phone'],
@@ -499,7 +481,7 @@ class DealController extends Controller
}
ActivityLog::create([
'tenant_id' => $tenant->id,
'tenant_id' => $tenantId,
'user_id' => null, // на prod — request()->user()->id
'deal_id' => $deal->id,
'event' => ActivityLog::EVENT_DEAL_CREATED,
@@ -6,7 +6,6 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Deal;
use App\Models\Tenant;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use OpenSpout\Common\Entity\Row;
@@ -21,13 +20,15 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
*
* Извлечено из DealController (Sprint 3 Phase A, audit O-refactor-01).
*
* J1 (Sprint 3F): auth:sanctum+tenant, tenant_id из auth()->user().
*
* O-perf-05: streaming устраняет memory pressure. PhpSpreadsheet строил
* полный объект .xlsx в памяти (для 10K сделок 100+ MB). OpenSpout пишет
* в php://output постранично через Writer + Row::fromValues и chunkById(500)
* по сделкам пик памяти O(1) от размера экспорта.
*
* API контракт сохранён:
* POST /api/deals/export {tenant_id, ids[], format?: csv|xlsx}
* POST /api/deals/export {ids[], format?: csv|xlsx}
* Headers Content-Type / Content-Disposition без изменений.
* CSV: UTF-8 + BOM + ;-разделитель (Excel-friendly RU-локаль).
* XLSX: bold-header + auto-size columns.
@@ -43,16 +44,12 @@ class DealExportController extends Controller
public function export(Request $request): StreamedResponse
{
$validated = $request->validate([
'tenant_id' => 'required|integer|min:1',
'ids' => 'required|array|min:1|max:10000',
'ids.*' => 'integer|min:1',
'format' => 'nullable|string|in:csv,xlsx',
]);
$tenant = Tenant::find($validated['tenant_id']);
if ($tenant === null) {
abort(404, 'Тенант не найден.');
}
$tenantId = (int) $request->user()->tenant_id;
$format = $validated['format'] ?? 'csv';
$filename = 'deals_export_'.now()->format('Y-m-d').'.'.$format;
@@ -67,13 +64,13 @@ class DealExportController extends Controller
'Content-Disposition' => 'attachment; filename="'.$filename.'"',
];
return new StreamedResponse(function () use ($validated, $tenant, $format) {
return new StreamedResponse(function () use ($validated, $tenantId, $format) {
// RLS-контекст должен быть установлен внутри транзакции на момент
// фактического SELECT. StreamedResponse callback вызывается уже
// после Laravel-response pipeline'а, поэтому открываем транзакцию
// прямо здесь.
DB::transaction(function () use ($validated, $tenant, $format) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenant->id);
DB::transaction(function () use ($validated, $tenantId, $format) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
$writer = $this->openWriter($format);
$writer->openToFile('php://output');
@@ -93,7 +90,7 @@ class DealExportController extends Controller
// chunkById(500) — keyset-friendly; в нашем DealsView это
// редкий тяжёлый action, экспортировать могут до 10K id.
Deal::query()
->where('tenant_id', $tenant->id)
->where('tenant_id', $tenantId)
->whereIn('id', $validated['ids'])
->orderBy('id')
->chunkById(500, function ($deals) use ($writer) {
@@ -123,15 +123,21 @@ class ImpersonationController extends Controller
]);
// TODO: отправить email на $tenant->contact_email с $plainCode.
// На MVP возвращаем code в response для тестов / dev (на prod НЕ должно
// возвращаться никогда — токен только в email клиента).
return response()->json([
$payload = [
'token_id' => $token->id,
'expires_at' => $token->expires_at->toIso8601String(),
'sent_to_email' => $token->sent_to_email,
// dev-only field — на prod исчезает после интеграции с MailService.
'_dev_plain_code' => $plainCode,
]);
];
// Audit-fix A2: plain-код возвращается в API-ответе ТОЛЬКО на dev/testing
// (для тестов и локальной разработки). На prod код уходит исключительно
// в email клиента — env-guard исключает захват impersonation-сессии
// через чтение ответа init.
if (app()->environment('local', 'testing')) {
$payload['_dev_plain_code'] = $plainCode;
}
return response()->json($payload);
}
/** POST /api/admin/impersonation/verify */
@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\ResolveUnknownStatusesRequest;
use App\Http\Requests\StoreImportRequest;
use App\Jobs\ImportLeadsJob;
use App\Models\ImportLog;
use App\Models\ImportUnknownStatus;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
/**
* CSV-импорт исторических лидов из crm.bp-gr.ru (ТЗ §6).
*
* Все маршруты под auth:sanctum + tenant (RLS-контекст задан middleware).
* tenant_id берётся из авторизованного пользователя, не из запроса.
*/
class ImportController extends Controller
{
/**
* POST /api/imports загрузка CSV, создание import_log, dispatch job'а.
*/
public function store(StoreImportRequest $request): JsonResponse
{
$tenantId = (int) $request->user()->tenant_id;
$file = $request->file('file');
$storedName = Str::uuid()->toString().'.csv';
$path = $file->storeAs("imports/{$tenantId}", $storedName, 'local');
$log = ImportLog::create([
'tenant_id' => $tenantId,
'user_id' => $request->user()->id,
'filename' => $file->getClientOriginalName(),
'file_path' => $path,
'status' => 'pending',
'entity_type' => 'leads',
'source_system' => 'crm.bp-gr.ru',
'dry_run' => $request->boolean('dry_run'),
]);
ImportLeadsJob::dispatch($log->id, $tenantId);
return response()->json(['data' => $this->toResource($log)], 201);
}
/**
* GET /api/imports история импортов тенанта (RLS отфильтрует по tenant).
*
* Defense-in-depth: явный where(tenant_id) поверх RLS на dev через
* `postgres` superuser RLS обходится BYPASSRLS, app-фильтр гарантирует
* изоляцию (паттерн из DealController).
*/
public function index(Request $request): JsonResponse
{
$tenantId = (int) $request->user()->tenant_id;
$logs = ImportLog::query()
->where('tenant_id', $tenantId)
->orderByDesc('id')
->limit(50)
->get()
->map(fn (ImportLog $log) => $this->toResource($log));
return response()->json(['data' => $logs]);
}
/**
* GET /api/imports/{importLog} прогресс одного импорта (для polling'а).
*
* Defense-in-depth: явная проверка tenant_id на принадлежность поверх RLS.
*/
public function show(Request $request, ImportLog $importLog): JsonResponse
{
$tenantId = (int) $request->user()->tenant_id;
abort_if($importLog->tenant_id !== $tenantId, 403, 'Доступ к импорту другого тенанта запрещён.');
return response()->json(['data' => $this->toResource($importLog)]);
}
/**
* GET /api/imports/unknown-statuses незамапленные статусы (вход wizard'а §6.6).
*
* Defense-in-depth: явный where(tenant_id) поверх RLS.
*/
public function unknownStatuses(Request $request): JsonResponse
{
$tenantId = (int) $request->user()->tenant_id;
$rows = ImportUnknownStatus::query()
->where('tenant_id', $tenantId)
->unresolved()
->orderByDesc('occurrences')
->get()
->map(fn (ImportUnknownStatus $s) => [
'id' => $s->id,
'status_ru' => $s->status_ru,
'occurrences' => $s->occurrences,
]);
return response()->json(['data' => $rows]);
}
/**
* POST /api/imports/unknown-statuses/resolve ручной маппинг статусов.
*
* Defense-in-depth: явный where(tenant_id) поверх RLS.
*/
public function resolveUnknownStatuses(ResolveUnknownStatusesRequest $request): JsonResponse
{
$tenantId = (int) $request->user()->tenant_id;
$userId = (int) $request->user()->id;
DB::transaction(function () use ($request, $tenantId, $userId): void {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
foreach ($request->validated()['mappings'] as $mapping) {
ImportUnknownStatus::query()
->where('tenant_id', $tenantId)
->where('status_ru', $mapping['status_ru'])
->update([
'mapped_to_slug' => $mapping['slug'],
'resolved_at' => now(),
'resolved_by' => $userId,
]);
}
});
return response()->json(['data' => ['resolved' => count($request->validated()['mappings'])]]);
}
/**
* @return array<string, mixed>
*/
private function toResource(ImportLog $log): array
{
return [
'id' => $log->id,
'filename' => $log->filename,
'status' => $log->status,
'rows_total' => $log->rows_total,
'rows_added' => $log->rows_added,
'rows_updated' => $log->rows_updated,
'rows_skipped' => $log->rows_skipped,
'unknown_statuses_count' => $log->unknown_statuses_count,
'dry_run' => $log->dry_run,
'error_message' => $log->error_message,
'started_at' => $log->started_at?->toIso8601String(),
'finished_at' => $log->finished_at?->toIso8601String(),
];
}
}
@@ -5,48 +5,160 @@ declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\BulkProjectActionRequest;
use App\Http\Requests\StoreProjectRequest;
use App\Http\Requests\UpdateProjectRequest;
use App\Http\Resources\ProjectResource;
use App\Models\Project;
use App\Models\Tenant;
use App\Services\Project\ProjectService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
/**
* Проекты tenant'а — для NewDealDialog dropdown'а и DealsView/Smart-filters.
* Проекты tenant'а расширенный API для ProjectsView + NewDealDialog.
*
* На MVP: tenant_id параметром. На prod: middleware('auth:sanctum')+'tenant'.
* index: фильтры по signal_type/status/search, пагинация, batch-fetch по ids.
* show: детальная карточка проекта с supplier_links.
*
* Auth: auth:sanctum + tenant middleware (устанавливает app.current_tenant_id для RLS).
* Task 2 Plan 5 заменяет MVP-версию (tenant_id параметром, без auth).
*/
class ProjectController extends Controller
{
/** GET /api/projects?tenant_id={id} */
public function __construct(private readonly ProjectService $projects) {}
/** GET /api/projects */
public function index(Request $request): JsonResponse
{
$tenantId = (int) $request->query('tenant_id', '0');
if ($tenantId < 1) {
return response()->json(['message' => 'Параметр tenant_id обязателен.'], 422);
$query = Project::query()
->with(['supplierB1', 'supplierB2', 'supplierB3']) // eager-load to avoid N+1 in aggregation helpers
->where('tenant_id', $request->user()->tenant_id);
// Batch-fetch по ids — возвращает без пагинации (для dropdown'ов и т.п.)
if ($ids = $request->query('ids')) {
// '?ids=' batch fetch. Non-numeric and zero values silently dropped via intval+filter
// (intval('abc')=0 → array_filter drops 0). Acceptable for a read-only dropdown:
// invalid input produces empty result, not 422.
$idArray = array_filter(array_map('intval', explode(',', (string) $ids)));
$items = $query->whereIn('id', $idArray)->get();
return response()->json(['data' => ProjectResource::collection($items)]);
}
$tenant = Tenant::find($tenantId);
if ($tenant === null) {
return response()->json(['message' => 'Тенант не найден.'], 404);
// Фильтр по типу сигнала
if ($type = $request->query('signal_type')) {
$query->where('signal_type', $type);
}
$projects = DB::transaction(function () use ($tenantId) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
// Фильтр по статусу жизненного цикла
$status = $request->query('status');
if ($status === 'archived') {
$query->archived();
} elseif ($status === 'active') {
$query->active()->where('is_active', true);
} elseif ($status === 'paused') {
$query->active()->where('is_active', false);
} else {
// По умолчанию: все не архивированные (active + paused)
$query->active();
}
return Project::query()
->where('is_active', true)
->orderBy('name')
->get(['id', 'name', 'tag', 'type']);
});
// Поиск по name и signal_identifier
if ($search = $request->query('search')) {
$query->where(function ($q) use ($search) {
$q->where('name', 'ilike', "%{$search}%")
->orWhere('signal_identifier', 'ilike', "%{$search}%");
});
}
$perPage = min((int) $request->query('per_page', '20'), 100);
$projects = $query->orderBy('created_at', 'desc')->paginate($perPage);
return response()->json([
'projects' => $projects->map(fn (Project $p) => [
'id' => $p->id,
'name' => $p->name,
'tag' => $p->tag,
'type' => $p->type,
]),
'data' => ProjectResource::collection($projects->items()),
'meta' => [
'current_page' => $projects->currentPage(),
'per_page' => $projects->perPage(),
'total' => $projects->total(),
],
]);
}
/** POST /api/projects */
public function store(StoreProjectRequest $request): JsonResponse
{
$project = $this->projects->create($request->user()->tenant, $request->validated());
return response()->json(['data' => new ProjectResource($project)], 201);
}
/** PATCH /api/projects/{id} */
public function update(UpdateProjectRequest $request, int $id): JsonResponse
{
$project = Project::where('tenant_id', $request->user()->tenant_id)->findOrFail($id);
$updated = $this->projects->update($project, $request->validated());
return response()->json(['data' => new ProjectResource($updated)]);
}
/** GET /api/projects/{id} */
public function show(Request $request, int $id): JsonResponse
{
$project = Project::with(['supplierB1', 'supplierB2', 'supplierB3']) // eager-load to avoid N+1
->where('tenant_id', $request->user()->tenant_id)
->findOrFail($id);
return response()->json(['data' => new ProjectResource($project)]);
}
/** DELETE /api/projects/{id} — soft-archive (sets archived_at, is_active=false) */
public function destroy(Request $request, int $id): JsonResponse
{
$project = Project::where('tenant_id', $request->user()->tenant_id)->findOrFail($id);
$this->projects->archive($project);
return response()->json(null, 204);
}
/** POST /api/projects/{id}/sync — re-dispatch SyncSupplierProjectJob */
public function sync(Request $request, int $id): JsonResponse
{
$project = Project::where('tenant_id', $request->user()->tenant_id)->findOrFail($id);
$this->projects->triggerSync($project);
return response()->json(['queued' => true, 'sync_status' => 'pending'], 202);
}
/** PATCH /api/projects/{id}/toggle-active — flip is_active flag */
public function toggleActive(Request $request, int $id): JsonResponse
{
$request->validate(['is_active' => ['required', 'boolean']]);
$project = Project::where('tenant_id', $request->user()->tenant_id)->findOrFail($id);
$project->update(['is_active' => $request->boolean('is_active')]);
return response()->json(['data' => new ProjectResource($project->fresh())]);
}
/** POST /api/projects/bulk — batch pause/resume/archive/update_regions/update_days/update_limit */
public function bulk(BulkProjectActionRequest $request): JsonResponse
{
$tenantId = $request->user()->tenant_id;
$ids = $this->projects->resolveBulkScope(
$tenantId,
$request->validated('ids'),
$request->validated('scope.filter'),
);
if (count($ids) > ProjectService::BULK_MAX) {
return response()->json([
'errors' => ['scope' => ['Слишком много проектов под фильтр (>500). Уточните фильтры или выберите вручную.']],
], 422);
}
$payload = array_merge($request->validated(), ['ids' => $ids]);
$result = $this->projects->bulkAction($tenantId, $request->validated('action'), $payload);
return response()->json($result);
}
}
@@ -13,7 +13,9 @@ use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\Response;
/**
* Reports API (schema §13.5). Все endpoint'ы под `auth:sanctum`.
@@ -340,6 +342,68 @@ class ReportJobController extends Controller
});
}
/**
* GET /api/reports/jobs/{id}/file?tenant=&expires=&signature= скачать
* готовый файл отчёта (F2, OPEN-И-20).
*
* Под `signed`-middleware (не auth:sanctum): подпись URL = capability-token.
* `tenant` в подписи нужен для RLS-контекста (нет авторизованного user'а).
* Подпись покрывает все query-параметры `tenant`/`id` подделать нельзя.
*/
public function download(Request $request, int $id): Response
{
$tenantId = (int) $request->query('tenant', '0');
return DB::transaction(function () use ($id, $tenantId): Response {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
$job = ReportJob::query()
->where('id', $id)
->where('tenant_id', $tenantId)
->first();
if ($job === null) {
return response()->json(['message' => 'Отчёт не найден.'], 404);
}
if ($job->status !== ReportJob::STATUS_DONE
|| $job->file_path === null
|| ($job->expires_at !== null && $job->expires_at->isPast())) {
return response()->json(['message' => 'Файл отчёта недоступен или истёк.'], 410);
}
if (! Storage::disk('local')->exists($job->file_path)) {
return response()->json(['message' => 'Файл отчёта не найден в хранилище.'], 404);
}
$extension = pathinfo($job->file_path, PATHINFO_EXTENSION);
return Storage::disk('local')->download(
$job->file_path,
sprintf('report-%d.%s', $job->id, $extension)
);
});
}
/**
* Signed URL (24 ч) на скачивание файла. NULL для не-готовых job'ов или
* после истечения retention (file_path обнулён cron'ом reports:cleanup-expired).
*/
private function downloadUrl(ReportJob $job): ?string
{
if ($job->status !== ReportJob::STATUS_DONE
|| $job->file_path === null
|| ($job->expires_at !== null && $job->expires_at->isPast())) {
return null;
}
return URL::temporarySignedRoute(
'reports.download',
Carbon::now()->addHours(24),
['id' => $job->id, 'tenant' => $job->tenant_id],
);
}
/** @return array<string, mixed> */
private function toResource(ReportJob $job): array
{
@@ -358,6 +422,7 @@ class ReportJobController extends Controller
'is_expired' => $job->expires_at !== null && $job->expires_at->isPast(),
'retry_count' => (int) ($job->parameters['retry_count'] ?? 0),
'retry_max' => self::RETRY_MAX_ATTEMPTS,
'download_url' => $this->downloadUrl($job),
];
}
}
@@ -10,6 +10,7 @@ use App\Models\SupplierLead;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\RateLimiter;
use Symfony\Component\HttpFoundation\IpUtils;
/**
@@ -40,6 +41,9 @@ use Symfony\Component\HttpFoundation\IpUtils;
*/
class SupplierWebhookController extends Controller
{
/** Audit-fix C2: per-IP rate-limit (DoS-guard), запросов в минуту. */
private const RATE_LIMIT_PER_MINUTE = 600;
public function receive(Request $request, string $secret): JsonResponse
{
if (! $this->verifySecret($secret)) {
@@ -50,6 +54,20 @@ class SupplierWebhookController extends Controller
return response()->json(['message' => 'Not found.'], 404);
}
// Audit-fix C2: per-IP rate-limit. Endpoint secret-gated, но защищаем
// от flood даже с валидным secret (DoS-guard). Лимит с запасом для
// легитимного stream'а лидов от crm.bp-gr.ru.
$rateKey = 'supplier-webhook:'.($request->ip() ?? 'unknown');
if (RateLimiter::tooManyAttempts($rateKey, self::RATE_LIMIT_PER_MINUTE)) {
$retryAfter = RateLimiter::availableIn($rateKey);
return response()->json([
'message' => 'Превышен лимит запросов.',
'retry_after' => $retryAfter,
], 429)->header('Retry-After', (string) $retryAfter);
}
RateLimiter::hit($rateKey, 60);
// Plan 2.6 fix #iii: timestamp partition guard. Партиции deals месячные
// (deals_2026_MM); time за пределами текущего месяца → INSERT CRASH
// "no partition of relation deals found for row" в RouteSupplierLeadJob.
@@ -0,0 +1,154 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\LeadCharge;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* Tenant-scoped доступ к lead_charges (read-only ledger) Plan 4 Task 11.
*
* RLS защищает изоляцию через SetTenantContext middleware
* (auth:sanctum + tenant). См. spec §6.3:
* docs/superpowers/specs/2026-05-11-plan4-billing-csv-admin-design.md.
*
* Эндпоинты:
* GET /api/billing/charges?page=N&period=current_month|last_month|90d&charge_source=prepaid|rub
* POST /api/billing/charges/export CSV через StreamedResponse + chunkById(500).
*
* RLS-контекст устанавливается на уровне middleware `tenant` (SetTenantContext),
* который оборачивает HTTP-запрос в транзакцию с `SET LOCAL app.current_tenant_id`.
* Для StreamedResponse callback'а транзакция middleware'а уже закрыта на момент
* вызова поэтому export открывает свою транзакцию внутри callback'а с явным
* SET LOCAL (паттерн из DealExportController).
*/
class TenantChargesController extends Controller
{
public function index(Request $request): JsonResponse
{
$tenantId = (int) $request->user()->tenant_id;
// Explicit tenant_id фильтр — defense-in-depth поверх RLS. В тестах PG
// superuser BYPASSRLS, и без явного where() запрос вернул бы строки
// других тенантов (см. InAppNotificationController паттерн).
$query = LeadCharge::query()
->where('tenant_id', $tenantId)
->orderBy('charged_at', 'desc');
$this->applyFilters($query, $request);
$page = $query->paginate(20);
return response()->json([
'data' => $page->items(),
'meta' => [
'current_page' => $page->currentPage(),
'last_page' => $page->lastPage(),
'total' => $page->total(),
'per_page' => $page->perPage(),
],
]);
}
public function export(Request $request): StreamedResponse
{
$tenantId = (int) $request->user()->tenant_id;
$period = $request->input('period');
$source = $request->input('charge_source');
$filename = 'charges_'.now()->format('Y-m-d_His').'.csv';
return new StreamedResponse(function () use ($tenantId, $period, $source) {
// RLS-контекст должен быть установлен внутри транзакции на момент
// фактического SELECT. StreamedResponse callback вызывается уже после
// закрытия middleware-транзакции, поэтому открываем свою явную.
// Паттерн из DealExportController.
DB::transaction(function () use ($tenantId, $period, $source) {
DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);
$out = fopen('php://output', 'w');
if ($out === false) {
return;
}
// BOM для Excel (CSV в UTF-8).
fwrite($out, "\xEF\xBB\xBF");
fputcsv($out, ['charged_at', 'deal_id', 'tier_no', 'charge_source', 'price_rub', 'balance_rub_after']);
// Explicit tenant_id фильтр — defense-in-depth поверх RLS
// (см. комментарий в index()).
$query = LeadCharge::query()
->where('tenant_id', $tenantId)
->orderBy('charged_at', 'desc');
$this->applyPeriodFilter($query, $period);
if ($source !== null && $source !== '') {
$query->where('charge_source', $source);
}
$query->chunkById(500, function ($charges) use ($out) {
foreach ($charges as $c) {
/** @var LeadCharge $c */
fputcsv($out, [
$c->charged_at->toIso8601String(),
(string) $c->deal_id,
(string) $c->tier_no,
(string) $c->getAttribute('charge_source'),
number_format($c->price_per_lead_kopecks / 100, 2, '.', ''),
// balance_rub_after — нет в lead_charges (доступно через
// balance_transactions). MVP оставляем пустым.
'',
]);
}
});
fclose($out);
});
}, 200, [
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => "attachment; filename=\"{$filename}\"",
]);
}
/**
* @param Builder<LeadCharge> $query
*/
private function applyFilters($query, Request $request): void
{
$this->applyPeriodFilter($query, $request->query('period'));
$source = $request->query('charge_source');
if (is_string($source) && $source !== '') {
$query->where('charge_source', $source);
}
}
/**
* @param Builder<LeadCharge> $query
*/
private function applyPeriodFilter($query, mixed $period): void
{
if (! is_string($period) || $period === '') {
return;
}
$now = Carbon::now('Europe/Moscow');
if ($period === 'current_month') {
$query->where('charged_at', '>=', $now->copy()->startOfMonth());
} elseif ($period === 'last_month') {
$query->whereBetween('charged_at', [
$now->copy()->subMonth()->startOfMonth(),
$now->copy()->subMonth()->endOfMonth(),
]);
} elseif ($period === '90d') {
$query->where('charged_at', '>=', $now->copy()->subDays(90));
}
}
}
@@ -117,17 +117,19 @@ class WebhookReceiveController extends Controller
}
/**
* HMAC-обязательность. Если ключ отсутствует в БД default false
* (backward-compat для существующих интеграций).
* HMAC-обязательность. Audit-fix B3: если ключ отсутствует в БД default
* TRUE (HMAC обязателен по умолчанию). Отключить можно только явной
* установкой webhook_hmac_required=false. Неизвестное значение fail-secure
* (HMAC требуется).
*/
private function isHmacRequired(): bool
{
$setting = SystemSetting::find('webhook_hmac_required');
if ($setting === null) {
return false;
return true;
}
return in_array($setting->value, ['true', '1'], true);
return ! in_array($setting->value, ['false', '0'], true);
}
/**

Some files were not shown because too many files have changed in this diff Show More