Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cf0f0c0c7 | |||
| de66b8b316 | |||
| 008c8a3ad0 | |||
| 18603f6881 | |||
| d7aa5efe30 | |||
| 21f5047640 | |||
| a539b08499 | |||
| 05706ef429 | |||
| 35b48c1b0c | |||
| 046c8b6efa | |||
| fc5f58a992 | |||
| b51d5fb31d | |||
| 10b19df1c4 | |||
| df4532d2fd | |||
| d85b9391cc | |||
| 2018959fdc | |||
| ff3979d527 | |||
| 756a8838d6 | |||
| a319e4f98a | |||
| 1313d89525 | |||
| bcce4d9986 | |||
| a718bb951f | |||
| 621498acc9 | |||
| cafa8dfe2d | |||
| 8d9183c3ac | |||
| 0cea2cc320 | |||
| 9b63e27825 | |||
| 0c98524357 | |||
| 431117087f | |||
| 5deff727a4 | |||
| 554b59359c | |||
| 507c4d869a | |||
| f9bedb6aad | |||
| 88eac07116 | |||
| b1e903f31a | |||
| ec6ebc57e0 | |||
| fad1c895a1 | |||
| 7b04e7e752 | |||
| 822e5346d8 | |||
| 4bdb996c6c | |||
| 830e7fc3d7 | |||
| c1ecefafc0 | |||
| f467409baf | |||
| c4876410ea |
@@ -0,0 +1,224 @@
|
||||
---
|
||||
name: data-scientist
|
||||
description: Expert data scientist for advanced analytics, machine learning, and statistical modeling. Handles complex data analysis, predictive modeling, and business intelligence.
|
||||
---
|
||||
|
||||
## Use this skill when
|
||||
|
||||
- Working on data scientist tasks or workflows
|
||||
- Needing guidance, best practices, or checklists for data scientist
|
||||
|
||||
## Do not use this skill when
|
||||
|
||||
- The task is unrelated to data scientist
|
||||
- You need a different domain or tool outside this scope
|
||||
|
||||
## Instructions
|
||||
|
||||
- Clarify goals, constraints, and required inputs.
|
||||
- Apply relevant best practices and validate outcomes.
|
||||
- Provide actionable steps and verification.
|
||||
|
||||
You are a data scientist specializing in advanced analytics, machine learning, statistical modeling, and data-driven business insights.
|
||||
|
||||
## Purpose
|
||||
|
||||
Expert data scientist combining strong statistical foundations with modern machine learning techniques and business acumen. Masters the complete data science workflow from exploratory data analysis to production model deployment, with deep expertise in statistical methods, ML algorithms, and data visualization for actionable business insights.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Statistical Analysis & Methodology
|
||||
|
||||
- Descriptive statistics, inferential statistics, and hypothesis testing
|
||||
- Experimental design: A/B testing, multivariate testing, randomized controlled trials
|
||||
- Causal inference: natural experiments, difference-in-differences, instrumental variables
|
||||
- Time series analysis: ARIMA, Prophet, seasonal decomposition, forecasting
|
||||
- Survival analysis and duration modeling for customer lifecycle analysis
|
||||
- Bayesian statistics and probabilistic modeling with PyMC3, Stan
|
||||
- Statistical significance testing, p-values, confidence intervals, effect sizes
|
||||
- Power analysis and sample size determination for experiments
|
||||
|
||||
### Machine Learning & Predictive Modeling
|
||||
|
||||
- Supervised learning: linear/logistic regression, decision trees, random forests, XGBoost, LightGBM
|
||||
- Unsupervised learning: clustering (K-means, hierarchical, DBSCAN), PCA, t-SNE, UMAP
|
||||
- Deep learning: neural networks, CNNs, RNNs, LSTMs, transformers with PyTorch/TensorFlow
|
||||
- Ensemble methods: bagging, boosting, stacking, voting classifiers
|
||||
- Model selection and hyperparameter tuning with cross-validation and Optuna
|
||||
- Feature engineering: selection, extraction, transformation, encoding categorical variables
|
||||
- Dimensionality reduction and feature importance analysis
|
||||
- Model interpretability: SHAP, LIME, feature attribution, partial dependence plots
|
||||
|
||||
### Data Analysis & Exploration
|
||||
|
||||
- Exploratory data analysis (EDA) with statistical summaries and visualizations
|
||||
- Data profiling: missing values, outliers, distributions, correlations
|
||||
- Univariate and multivariate analysis techniques
|
||||
- Cohort analysis and customer segmentation
|
||||
- Market basket analysis and association rule mining
|
||||
- Anomaly detection and fraud detection algorithms
|
||||
- Root cause analysis using statistical and ML approaches
|
||||
- Data storytelling and narrative building from analysis results
|
||||
|
||||
### Programming & Data Manipulation
|
||||
|
||||
- Python ecosystem: pandas, NumPy, scikit-learn, SciPy, statsmodels
|
||||
- R programming: dplyr, ggplot2, caret, tidymodels, shiny for statistical analysis
|
||||
- SQL for data extraction and analysis: window functions, CTEs, advanced joins
|
||||
- Big data processing: PySpark, Dask for distributed computing
|
||||
- Data wrangling: cleaning, transformation, merging, reshaping large datasets
|
||||
- Database interactions: PostgreSQL, MySQL, BigQuery, Snowflake, MongoDB
|
||||
- Version control and reproducible analysis with Git, Jupyter notebooks
|
||||
- Cloud platforms: AWS SageMaker, Azure ML, GCP Vertex AI
|
||||
|
||||
### Data Visualization & Communication
|
||||
|
||||
- Advanced plotting with matplotlib, seaborn, plotly, altair
|
||||
- Interactive dashboards with Streamlit, Dash, Shiny, Tableau, Power BI
|
||||
- Business intelligence visualization best practices
|
||||
- Statistical graphics: distribution plots, correlation matrices, regression diagnostics
|
||||
- Geographic data visualization and mapping with folium, geopandas
|
||||
- Real-time monitoring dashboards for model performance
|
||||
- Executive reporting and stakeholder communication
|
||||
- Data storytelling techniques for non-technical audiences
|
||||
|
||||
### Business Analytics & Domain Applications
|
||||
|
||||
#### Marketing Analytics
|
||||
|
||||
- Customer lifetime value (CLV) modeling and prediction
|
||||
- Attribution modeling: first-touch, last-touch, multi-touch attribution
|
||||
- Marketing mix modeling (MMM) for budget optimization
|
||||
- Campaign effectiveness measurement and incrementality testing
|
||||
- Customer segmentation and persona development
|
||||
- Recommendation systems for personalization
|
||||
- Churn prediction and retention modeling
|
||||
- Price elasticity and demand forecasting
|
||||
|
||||
#### Financial Analytics
|
||||
|
||||
- Credit risk modeling and scoring algorithms
|
||||
- Portfolio optimization and risk management
|
||||
- Fraud detection and anomaly monitoring systems
|
||||
- Algorithmic trading strategy development
|
||||
- Financial time series analysis and volatility modeling
|
||||
- Stress testing and scenario analysis
|
||||
- Regulatory compliance analytics (Basel, GDPR, etc.)
|
||||
- Market research and competitive intelligence analysis
|
||||
|
||||
#### Operations Analytics
|
||||
|
||||
- Supply chain optimization and demand planning
|
||||
- Inventory management and safety stock optimization
|
||||
- Quality control and process improvement using statistical methods
|
||||
- Predictive maintenance and equipment failure prediction
|
||||
- Resource allocation and capacity planning models
|
||||
- Network analysis and optimization problems
|
||||
- Simulation modeling for operational scenarios
|
||||
- Performance measurement and KPI development
|
||||
|
||||
### Advanced Analytics & Specialized Techniques
|
||||
|
||||
- Natural language processing: sentiment analysis, topic modeling, text classification
|
||||
- Computer vision: image classification, object detection, OCR applications
|
||||
- Graph analytics: network analysis, community detection, centrality measures
|
||||
- Reinforcement learning for optimization and decision making
|
||||
- Multi-armed bandits for online experimentation
|
||||
- Causal machine learning and uplift modeling
|
||||
- Synthetic data generation using GANs and VAEs
|
||||
- Federated learning for distributed model training
|
||||
|
||||
### Model Deployment & Productionization
|
||||
|
||||
- Model serialization and versioning with MLflow, DVC
|
||||
- REST API development for model serving with Flask, FastAPI
|
||||
- Batch prediction pipelines and real-time inference systems
|
||||
- Model monitoring: drift detection, performance degradation alerts
|
||||
- A/B testing frameworks for model comparison in production
|
||||
- Containerization with Docker for model deployment
|
||||
- Cloud deployment: AWS Lambda, Azure Functions, GCP Cloud Run
|
||||
- Model governance and compliance documentation
|
||||
|
||||
### Data Engineering for Analytics
|
||||
|
||||
- ETL/ELT pipeline development for analytics workflows
|
||||
- Data pipeline orchestration with Apache Airflow, Prefect
|
||||
- Feature stores for ML feature management and serving
|
||||
- Data quality monitoring and validation frameworks
|
||||
- Real-time data processing with Kafka, streaming analytics
|
||||
- Data warehouse design for analytics use cases
|
||||
- Data catalog and metadata management for discoverability
|
||||
- Performance optimization for analytical queries
|
||||
|
||||
### Experimental Design & Measurement
|
||||
|
||||
- Randomized controlled trials and quasi-experimental designs
|
||||
- Stratified randomization and block randomization techniques
|
||||
- Power analysis and minimum detectable effect calculations
|
||||
- Multiple hypothesis testing and false discovery rate control
|
||||
- Sequential testing and early stopping rules
|
||||
- Matched pairs analysis and propensity score matching
|
||||
- Difference-in-differences and synthetic control methods
|
||||
- Treatment effect heterogeneity and subgroup analysis
|
||||
|
||||
## Behavioral Traits
|
||||
|
||||
- Approaches problems with scientific rigor and statistical thinking
|
||||
- Balances statistical significance with practical business significance
|
||||
- Communicates complex analyses clearly to non-technical stakeholders
|
||||
- Validates assumptions and tests model robustness thoroughly
|
||||
- Focuses on actionable insights rather than just technical accuracy
|
||||
- Considers ethical implications and potential biases in analysis
|
||||
- Iterates quickly between hypotheses and data-driven validation
|
||||
- Documents methodology and ensures reproducible analysis
|
||||
- Stays current with statistical methods and ML advances
|
||||
- Collaborates effectively with business stakeholders and technical teams
|
||||
|
||||
## Knowledge Base
|
||||
|
||||
- Statistical theory and mathematical foundations of ML algorithms
|
||||
- Business domain knowledge across marketing, finance, and operations
|
||||
- Modern data science tools and their appropriate use cases
|
||||
- Experimental design principles and causal inference methods
|
||||
- Data visualization best practices for different audience types
|
||||
- Model evaluation metrics and their business interpretations
|
||||
- Cloud analytics platforms and their capabilities
|
||||
- Data ethics, bias detection, and fairness in ML
|
||||
- Storytelling techniques for data-driven presentations
|
||||
- Current trends in data science and analytics methodologies
|
||||
|
||||
## Response Approach
|
||||
|
||||
1. **Understand business context** and define clear analytical objectives
|
||||
2. **Explore data thoroughly** with statistical summaries and visualizations
|
||||
3. **Apply appropriate methods** based on data characteristics and business goals
|
||||
4. **Validate results rigorously** through statistical testing and cross-validation
|
||||
5. **Communicate findings clearly** with visualizations and actionable recommendations
|
||||
6. **Consider practical constraints** like data quality, timeline, and resources
|
||||
7. **Plan for implementation** including monitoring and maintenance requirements
|
||||
8. **Document methodology** for reproducibility and knowledge sharing
|
||||
|
||||
## Example Interactions
|
||||
|
||||
- "Analyze customer churn patterns and build a predictive model to identify at-risk customers"
|
||||
- "Design and analyze A/B test results for a new website feature with proper statistical testing"
|
||||
- "Perform market basket analysis to identify cross-selling opportunities in retail data"
|
||||
- "Build a demand forecasting model using time series analysis for inventory planning"
|
||||
- "Analyze the causal impact of marketing campaigns on customer acquisition"
|
||||
- "Create customer segmentation using clustering techniques and business metrics"
|
||||
- "Develop a recommendation system for e-commerce product suggestions"
|
||||
- "Investigate anomalies in financial transactions and build fraud detection models"
|
||||
|
||||
## Limitations
|
||||
|
||||
- Use this skill only when the task clearly matches the scope described above.
|
||||
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
|
||||
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
|
||||
|
||||
---
|
||||
|
||||
> **Provenance (A11 «ML / AI-разработка»):** vendored into Лидерра 2026-05-17 from
|
||||
> [`sickn33/antigravity-awesome-skills`](https://github.com/sickn33/antigravity-awesome-skills)
|
||||
> `skills/data-scientist`. Skill content is licensed **CC BY 4.0**; repository
|
||||
> tooling is MIT. Aggregator frontmatter (`risk`/`source`/`date_added`) dropped on
|
||||
> vendor. See `docs/ml/README.md` for the A11 toolset and boundaries.
|
||||
@@ -4,3 +4,4 @@ bin/
|
||||
CLAUDE.md
|
||||
.claude/skills/mermaid/
|
||||
.claude/skills/ccpm/
|
||||
.claude/skills/data-scientist/
|
||||
|
||||
@@ -43,6 +43,20 @@
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
/.deptrac.cache
|
||||
/.codex
|
||||
/.cursor/
|
||||
/.idea
|
||||
|
||||
@@ -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).'}';
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,11 @@ class StoreProjectRequest extends FormRequest
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'signal_type' => ['required', Rule::in(['site', 'call', 'sms'])],
|
||||
'daily_limit_target' => ['required', 'integer', 'min:1', 'max:10000'],
|
||||
'region_mask' => ['required', 'integer', 'min:0'],
|
||||
'region_mode' => ['required', Rule::in(['include', 'exclude'])],
|
||||
// Plan 6: subject-level regions[] заменил region_mask/region_mode на API-уровне.
|
||||
// Empty array = "вся РФ" (паритет с legacy region_mask=255 + region_mode='include').
|
||||
// present = поле должно быть в payload (даже если []), enforces explicit choice.
|
||||
'regions' => ['present', 'array'],
|
||||
'regions.*' => ['integer', 'between:1,89'],
|
||||
'delivery_days_mask' => ['required', 'integer', 'min:1', 'max:127'],
|
||||
];
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateProjectRequest extends FormRequest
|
||||
{
|
||||
@@ -20,8 +19,10 @@ class UpdateProjectRequest extends FormRequest
|
||||
return [
|
||||
'name' => ['sometimes', 'string', 'max:255'],
|
||||
'daily_limit_target' => ['sometimes', 'integer', 'min:1', 'max:10000'],
|
||||
'region_mask' => ['sometimes', 'integer', 'min:0'],
|
||||
'region_mode' => ['sometimes', Rule::in(['include', 'exclude'])],
|
||||
// Plan 6: subject-level regions[] заменил region_mask/region_mode на API-уровне.
|
||||
// sometimes = поле omit-able (preserves prior DB value), массив + each 1..89.
|
||||
'regions' => ['sometimes', 'array'],
|
||||
'regions.*' => ['integer', 'between:1,89'],
|
||||
'delivery_days_mask' => ['sometimes', 'integer', 'min:1', 'max:127'],
|
||||
'sms_senders' => ['sometimes', 'array', 'min:1'],
|
||||
'sms_senders.*' => ['string', 'max:11'],
|
||||
|
||||
@@ -31,6 +31,7 @@ class ProjectResource extends JsonResource
|
||||
'archived_at' => $project->archived_at?->toIso8601String(),
|
||||
'region_mask' => $this->region_mask,
|
||||
'region_mode' => $this->region_mode,
|
||||
'regions' => $this->regions,
|
||||
'delivery_days_mask' => $this->delivery_days_mask,
|
||||
'sync_status' => $this->aggregateSyncStatus(),
|
||||
'last_synced_at' => $this->aggregateLastSyncedAt(),
|
||||
|
||||
@@ -207,7 +207,7 @@ class SyncSupplierProjectsJob implements ShouldQueue
|
||||
* Маппинг:
|
||||
* daily_limit ← daily_limit_target
|
||||
* workdays ← биты delivery_days_mask (bit 0=Пн, …, bit 6=Вс) → ISO 1..7
|
||||
* regions ← биты region_mask (bit 0=Центральный, …, bit 7=Дальневосточный) → 1..8
|
||||
* regions ← projects.regions INT[] (subject codes 1..89) direct copy
|
||||
*
|
||||
* @param EloquentCollection<int, Project> $projects
|
||||
* @return Collection<int, stdClass>
|
||||
@@ -219,12 +219,11 @@ class SyncSupplierProjectsJob implements ShouldQueue
|
||||
$obj->daily_limit = (int) $p->daily_limit_target;
|
||||
$obj->workdays = $this->bitmaskToList((int) $p->delivery_days_mask, 7);
|
||||
|
||||
// region_mask=255 (все 8 ФО, default) — catch-all семантика → пустой массив
|
||||
// у supplier ("без региональных ограничений"). Иначе — список выставленных битов.
|
||||
$regionMask = (int) $p->region_mask;
|
||||
$obj->regions = $regionMask === 255
|
||||
? []
|
||||
: $this->bitmaskToList($regionMask, 8);
|
||||
// Plan 6: projects.regions[] напрямую копируется в supplier_projects.current_regions.
|
||||
// Empty array = "вся РФ" (паритет с supplier API semantics).
|
||||
// Legacy region_mask/region_mode игнорируются — они dual-write для PhonePrefixService,
|
||||
// outbound к supplier использует только regions[]. Cleanup в Plan 6.5.
|
||||
$obj->regions = array_values((array) $p->regions);
|
||||
|
||||
return $obj;
|
||||
})->values();
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\PostgresIntArray;
|
||||
use Carbon\CarbonInterface;
|
||||
use Database\Factories\ProjectFactory;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -45,6 +46,9 @@ class Project extends Model
|
||||
'effective_limit_calculated_at',
|
||||
'region_mask',
|
||||
'region_mode',
|
||||
// Plan 6 (schema v8.20): Subject-level regions array (89 codes из resources/js/constants/regions.ts).
|
||||
// Источник истины с Plan 6+; region_mask/region_mode — DEPRECATED (Plan 6.5 cleanup).
|
||||
'regions',
|
||||
'delivery_days_mask',
|
||||
'assignment_strategy',
|
||||
'ttfr_target_minutes',
|
||||
@@ -69,6 +73,10 @@ class Project extends Model
|
||||
'daily_limit_target' => 'integer',
|
||||
'effective_daily_limit_today' => 'integer',
|
||||
'region_mask' => 'integer',
|
||||
// Plan 6: Subject-level regions array (89 codes). Используется кастомный
|
||||
// PostgresIntArray cast — Laravel stock 'array' посылает JSON `[1,2,3]`,
|
||||
// что Postgres отвергает на INT[] (ожидает literal `{1,2,3}`).
|
||||
'regions' => PostgresIntArray::class,
|
||||
'delivery_days_mask' => 'integer',
|
||||
'ttfr_target_minutes' => 'integer',
|
||||
'effective_limit_calculated_at' => 'datetime',
|
||||
|
||||
@@ -114,6 +114,13 @@ class ProjectService
|
||||
return ['updated' => $updated, 'skipped' => [], 'warnings' => []];
|
||||
}
|
||||
|
||||
/**
|
||||
* LEGACY (Plan 6): обновляет только bitmask `region_mask` федеральных округов.
|
||||
* После Plan 6 источник истины региональной фильтрации — `regions` INT[];
|
||||
* outbound SyncSupplierProjectsJob читает `regions[]`, НЕ `region_mask`. Значит
|
||||
* этот bulk-action на реальную фильтрацию у поставщика не влияет. Субъект-уровневый
|
||||
* bulk-edit `regions[]` запланирован в Plan 6.5 (spec §13 — out of scope C9).
|
||||
*/
|
||||
private function bulkUpdateRegions($query, array $payload): array
|
||||
{
|
||||
$add = (int) ($payload['add'] ?? 0);
|
||||
@@ -191,6 +198,11 @@ class ProjectService
|
||||
|
||||
$data['tenant_id'] = $tenant->id;
|
||||
$data['is_active'] = true;
|
||||
$data['regions'] = $data['regions'] ?? [];
|
||||
// Plan 6 dual-write: regions[] источник истины; region_mask/mode — legacy для
|
||||
// PhonePrefixService / LeadRouter, удаляются в Plan 6.5 после переключения читателей.
|
||||
$data['region_mask'] = 255;
|
||||
$data['region_mode'] = 'include';
|
||||
$project = Project::create($data);
|
||||
|
||||
SyncSupplierProjectJob::dispatch($project->id);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "*",
|
||||
"deptrac/deptrac": "^4.6",
|
||||
"fakerphp/faker": "^1.23",
|
||||
"infection/infection": "^0.32.7",
|
||||
"larastan/larastan": "*",
|
||||
|
||||
Generated
+427
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f6418ddc96f575de868a519b516c26d8",
|
||||
"content-hash": "b859d747b77450b0917b3a7ae30284aa",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@@ -7279,6 +7279,91 @@
|
||||
],
|
||||
"time": "2024-05-06T16:37:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "deptrac/deptrac",
|
||||
"version": "4.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/deptrac/deptrac.git",
|
||||
"reference": "6ff20dec210f119a4ddebdf8e28603689f34eb67"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/deptrac/deptrac/zipball/6ff20dec210f119a4ddebdf8e28603689f34eb67",
|
||||
"reference": "6ff20dec210f119a4ddebdf8e28603689f34eb67",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer/xdebug-handler": "^3.0",
|
||||
"jetbrains/phpstorm-stubs": "2024.3 || 2025.3 || 2026.1",
|
||||
"nikic/php-parser": "^5",
|
||||
"php": "^8.2",
|
||||
"phpdocumentor/graphviz": "^2.1",
|
||||
"phpdocumentor/type-resolver": "^1.9.0 || ^2.0.0",
|
||||
"phpstan/phpdoc-parser": "^1.5.0 || ^2.1.0",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"psr/container": "^2.0",
|
||||
"psr/event-dispatcher": "^1.0",
|
||||
"symfony/config": "^6.4 || ^7.4 || ^8.0",
|
||||
"symfony/console": "^6.4 || ^7.4 || ^8.0",
|
||||
"symfony/dependency-injection": "^6.4 || ^7.4 || ^8.0",
|
||||
"symfony/event-dispatcher": "^6.4 || ^7.4 || ^8.0",
|
||||
"symfony/event-dispatcher-contracts": "^3.4",
|
||||
"symfony/filesystem": "^6.4 || ^7.4 || ^8.0",
|
||||
"symfony/finder": "^6.4 || ^7.4 || ^8.0",
|
||||
"symfony/yaml": "^6.4 || ^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8",
|
||||
"ergebnis/composer-normalize": "^2.45",
|
||||
"ext-libxml": "*",
|
||||
"symfony/stopwatch": "^6.4 || ^7.4 || ^8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "For using the JUnit output formatter"
|
||||
},
|
||||
"bin": [
|
||||
"deptrac"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": false,
|
||||
"forward-command": true,
|
||||
"target-directory": "tools"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Deptrac\\Deptrac\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Tim Glabisch"
|
||||
},
|
||||
{
|
||||
"name": "Simon Mönch"
|
||||
},
|
||||
{
|
||||
"name": "Denis Brumann"
|
||||
}
|
||||
],
|
||||
"description": "Deptrac is a static code analysis tool that helps to enforce rules for dependencies between software layers.",
|
||||
"keywords": [
|
||||
"dev",
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/deptrac/deptrac/issues",
|
||||
"source": "https://github.com/deptrac/deptrac/tree/4.6.1"
|
||||
},
|
||||
"time": "2026-05-13T08:23:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
"version": "1.1.6",
|
||||
@@ -8042,6 +8127,50 @@
|
||||
},
|
||||
"time": "2025-03-19T14:43:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jetbrains/phpstorm-stubs",
|
||||
"version": "v2026.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/JetBrains/phpstorm-stubs",
|
||||
"reference": "2cdd054c4109dfb76667c9198bf9427606354243"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/2cdd054c4109dfb76667c9198bf9427606354243",
|
||||
"reference": "2cdd054c4109dfb76667c9198bf9427606354243",
|
||||
"shasum": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^v3.86",
|
||||
"nikic/php-parser": "^v5.6",
|
||||
"phpdocumentor/reflection-docblock": "^5.6",
|
||||
"phpunit/phpunit": "^12.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"PhpStormStubsMap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"description": "PHP runtime & extensions header files for PhpStorm",
|
||||
"homepage": "https://www.jetbrains.com/phpstorm",
|
||||
"keywords": [
|
||||
"autocomplete",
|
||||
"code",
|
||||
"inference",
|
||||
"inspection",
|
||||
"jetbrains",
|
||||
"phpstorm",
|
||||
"stubs",
|
||||
"type"
|
||||
],
|
||||
"time": "2026-02-19T20:12:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "justinrainbow/json-schema",
|
||||
"version": "6.8.2",
|
||||
@@ -9674,6 +9803,59 @@
|
||||
},
|
||||
"time": "2022-02-21T01:04:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/graphviz",
|
||||
"version": "2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/GraphViz.git",
|
||||
"reference": "115999dc7f31f2392645aa825a94a6b165e1cedf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/GraphViz/zipball/115999dc7f31f2392645aa825a94a6b165e1cedf",
|
||||
"reference": "115999dc7f31f2392645aa825a94a6b165e1cedf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-simplexml": "*",
|
||||
"mockery/mockery": "^1.2",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpunit/phpunit": "^8.2 || ^9.2",
|
||||
"psalm/phar": "^4.15"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"phpDocumentor\\GraphViz\\": "src/phpDocumentor/GraphViz",
|
||||
"phpDocumentor\\GraphViz\\PHPStan\\": "./src/phpDocumentor/PHPStan"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mike van Riel",
|
||||
"email": "mike.vanriel@naenius.com"
|
||||
}
|
||||
],
|
||||
"description": "Wrapper for Graphviz",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpDocumentor/GraphViz/issues",
|
||||
"source": "https://github.com/phpDocumentor/GraphViz/tree/2.1.0"
|
||||
},
|
||||
"time": "2021-12-13T19:03:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-common",
|
||||
"version": "2.2.0",
|
||||
@@ -12674,6 +12856,169 @@
|
||||
],
|
||||
"time": "2024-10-20T05:08:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/config",
|
||||
"version": "v7.4.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/config.git",
|
||||
"reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57",
|
||||
"reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/filesystem": "^7.1|^8.0",
|
||||
"symfony/polyfill-ctype": "~1.8"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/finder": "<6.4",
|
||||
"symfony/service-contracts": "<2.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/event-dispatcher": "^6.4|^7.0|^8.0",
|
||||
"symfony/finder": "^6.4|^7.0|^8.0",
|
||||
"symfony/messenger": "^6.4|^7.0|^8.0",
|
||||
"symfony/service-contracts": "^2.5|^3",
|
||||
"symfony/yaml": "^6.4|^7.0|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Config\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/config/tree/v7.4.10"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-03T14:20:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/dependency-injection",
|
||||
"version": "v7.4.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dependency-injection.git",
|
||||
"reference": "4eb0d9dfa9d4f7c59216baf49b3ed6b1fb72293d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/4eb0d9dfa9d4f7c59216baf49b3ed6b1fb72293d",
|
||||
"reference": "4eb0d9dfa9d4f7c59216baf49b3ed6b1fb72293d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"psr/container": "^1.1|^2.0",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/service-contracts": "^3.6",
|
||||
"symfony/var-exporter": "^6.4.20|^7.2.5|^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"ext-psr": "<1.1|>=2",
|
||||
"symfony/config": "<6.4",
|
||||
"symfony/finder": "<6.4",
|
||||
"symfony/yaml": "<6.4"
|
||||
},
|
||||
"provide": {
|
||||
"psr/container-implementation": "1.1|2.0",
|
||||
"symfony/service-implementation": "1.1|2.0|3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/config": "^6.4|^7.0|^8.0",
|
||||
"symfony/expression-language": "^6.4|^7.0|^8.0",
|
||||
"symfony/yaml": "^6.4|^7.0|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\DependencyInjection\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/dependency-injection/tree/v7.4.10"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-06T11:55:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v7.4.9",
|
||||
@@ -12744,6 +13089,87 @@
|
||||
],
|
||||
"time": "2026-04-18T13:18:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-exporter",
|
||||
"version": "v7.4.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-exporter.git",
|
||||
"reference": "22e03a49c95ef054a43601cd159b222bfab1c701"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/22e03a49c95ef054a43601cd159b222bfab1c701",
|
||||
"reference": "22e03a49c95ef054a43601cd159b222bfab1c701",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"symfony/deprecation-contracts": "^2.5|^3"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/property-access": "^6.4|^7.0|^8.0",
|
||||
"symfony/serializer": "^6.4|^7.0|^8.0",
|
||||
"symfony/var-dumper": "^6.4|^7.0|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\VarExporter\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Allows exporting any serializable PHP data structure to plain PHP code",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"clone",
|
||||
"construct",
|
||||
"export",
|
||||
"hydrate",
|
||||
"instantiate",
|
||||
"lazy-loading",
|
||||
"proxy",
|
||||
"serialize"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/var-exporter/tree/v7.4.9"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-18T13:18:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v7.4.10",
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Plan 6 (C9) — subject-level regions.
|
||||
*
|
||||
* +1 колонка projects.regions INT[] (1..89 коды субъектов РФ; пустой массив = вся РФ).
|
||||
* +1 GIN-индекс idx_projects_regions для outbound regions queries.
|
||||
* region_mask/region_mode остаются (dual-write) — удаление в Plan 6.5.
|
||||
*
|
||||
* Guard'ы: migrate:fresh грузит schema.sql v8.22 (где delta уже есть) до миграций,
|
||||
* поэтому каждый кусок применяется только при отсутствии (как Sprint 4 миграция).
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasColumn('projects', 'regions')) {
|
||||
DB::statement("ALTER TABLE projects ADD COLUMN regions INT[] NOT NULL DEFAULT '{}'::INT[]");
|
||||
}
|
||||
|
||||
DB::statement('CREATE INDEX IF NOT EXISTS idx_projects_regions ON projects USING GIN (regions)');
|
||||
|
||||
DB::statement(
|
||||
'COMMENT ON COLUMN projects.regions IS '
|
||||
."'Subject-level region filter (1..89 коды субъектов РФ). Пустой массив = вся РФ. Plan 6 (v8.22).'"
|
||||
);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::statement('DROP INDEX IF EXISTS idx_projects_regions');
|
||||
|
||||
if (Schema::hasColumn('projects', 'regions')) {
|
||||
Schema::table('projects', fn ($table) => $table->dropColumn('regions'));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
deptrac:
|
||||
paths:
|
||||
- ./app
|
||||
layers:
|
||||
- name: Controller
|
||||
collectors: [{ type: directory, value: app/Http/Controllers/.* }]
|
||||
- name: Request
|
||||
collectors: [{ type: directory, value: app/Http/Requests/.* }]
|
||||
- name: Resource
|
||||
collectors: [{ type: directory, value: app/Http/Resources/.* }]
|
||||
- name: Middleware
|
||||
collectors: [{ type: directory, value: app/Http/Middleware/.* }]
|
||||
- name: Service
|
||||
collectors: [{ type: directory, value: app/Services/.* }]
|
||||
- name: Job
|
||||
collectors: [{ type: directory, value: app/Jobs/.* }]
|
||||
- name: Console
|
||||
collectors: [{ type: directory, value: app/Console/.* }]
|
||||
- name: Repository
|
||||
collectors: [{ type: directory, value: app/Repositories/.* }]
|
||||
- name: Model
|
||||
collectors: [{ type: directory, value: app/Models/.* }]
|
||||
- name: Mail
|
||||
collectors: [{ type: directory, value: app/Mail/.* }]
|
||||
- name: Rule
|
||||
collectors: [{ type: directory, value: app/Rules/.* }]
|
||||
- name: Exception
|
||||
collectors: [{ type: directory, value: app/Exceptions/.* }]
|
||||
- name: Provider
|
||||
collectors: [{ type: directory, value: app/Providers/.* }]
|
||||
ruleset:
|
||||
# Conservative ruleset — enforces only the architecturally-wrong directions
|
||||
# (inward/upward deps). Whatever current code violates is captured by the
|
||||
# baseline (deptrac.baseline.yaml); this gate then catches only NEW drift.
|
||||
Controller: [Service, Request, Resource, Model, Job, Mail, Repository, Rule, Exception]
|
||||
Middleware: [Service, Model, Exception]
|
||||
Service: [Service, Model, Repository, Job, Mail, Rule, Exception]
|
||||
Job: [Service, Model, Repository, Mail, Exception]
|
||||
Console: [Service, Model, Repository, Job, Mail, Exception]
|
||||
Repository: [Model, Exception]
|
||||
Request: [Rule, Model]
|
||||
Resource: [Model]
|
||||
Rule: [Model]
|
||||
Mail: [Model]
|
||||
Model: []
|
||||
Provider: [Controller, Service, Job, Console, Repository, Model, Mail, Middleware, Request, Resource, Rule, Exception]
|
||||
+229
-7
@@ -66,6 +66,96 @@ parameters:
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImpersonationController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$dry_run\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$error_message\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$filename\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$finished_at\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$rows_added\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$rows_skipped\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$rows_total\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$rows_updated\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$started_at\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$status\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$tenant_id\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$unknown_statuses_count\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportUnknownStatus\:\:\$occurrences\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportUnknownStatus\:\:\$status_ru\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\<int,App\\Models\\ImportUnknownStatus\>\:\:map\(\) contains unresolvable type\.$#'
|
||||
identifier: argument.unresolvableType
|
||||
count: 1
|
||||
path: app/Http/Controllers/Api/ImportController.php
|
||||
|
||||
-
|
||||
message: '#^Using nullsafe method call on non\-nullable type Illuminate\\Support\\Carbon\. Use \-\> instead\.$#'
|
||||
identifier: nullsafe.neverNull
|
||||
@@ -78,12 +168,48 @@ parameters:
|
||||
count: 1
|
||||
path: app/Http/Middleware/SetTenantContext.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$file_path\.$#'
|
||||
identifier: property.notFound
|
||||
count: 3
|
||||
path: app/Jobs/ImportLeadsJob.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$user_id\.$#'
|
||||
identifier: property.notFound
|
||||
count: 3
|
||||
path: app/Jobs/ImportLeadsJob.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$array \(array\{string\}\) of array_values is already a list, call has no effect\.$#'
|
||||
identifier: arrayValues.list
|
||||
count: 1
|
||||
path: app/Jobs/Supplier/SyncSupplierProjectsJob.php
|
||||
|
||||
-
|
||||
message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
|
||||
identifier: nullsafe.neverNull
|
||||
count: 2
|
||||
path: app/Mail/NewLeadNotification.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @mixin contains unknown class App\\Models\\IdeHelperImportLog\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: app/Models/ImportLog.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @mixin contains unknown class App\\Models\\IdeHelperImportUnknownStatus\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: app/Models/ImportUnknownStatus.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$dry_run\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: app/Services/Import/HistoricalImportService.php
|
||||
|
||||
-
|
||||
message: '#^Call to function is_array\(\) with array\<mixed\> will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
@@ -159,7 +285,7 @@ parameters:
|
||||
-
|
||||
message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:postJson\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
count: 6
|
||||
count: 9
|
||||
path: tests/Feature/Admin/AdminPricingTiersControllerTest.php
|
||||
|
||||
-
|
||||
@@ -765,13 +891,13 @@ parameters:
|
||||
-
|
||||
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$otherTenant\.$#'
|
||||
identifier: property.notFound
|
||||
count: 7
|
||||
count: 10
|
||||
path: tests/Feature/DealIndexTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$project\.$#'
|
||||
identifier: property.notFound
|
||||
count: 26
|
||||
count: 32
|
||||
path: tests/Feature/DealIndexTest.php
|
||||
|
||||
-
|
||||
@@ -783,7 +909,7 @@ parameters:
|
||||
-
|
||||
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$tenant\.$#'
|
||||
identifier: property.notFound
|
||||
count: 30
|
||||
count: 36
|
||||
path: tests/Feature/DealIndexTest.php
|
||||
|
||||
-
|
||||
@@ -801,7 +927,7 @@ parameters:
|
||||
-
|
||||
message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:getJson\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
count: 21
|
||||
count: 24
|
||||
path: tests/Feature/DealIndexTest.php
|
||||
|
||||
-
|
||||
@@ -972,6 +1098,12 @@ parameters:
|
||||
count: 9
|
||||
path: tests/Feature/DealUpdateTest.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:seed\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
count: 2
|
||||
path: tests/Feature/DemoSeederTest.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:postJson\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
@@ -1008,6 +1140,18 @@ parameters:
|
||||
count: 17
|
||||
path: tests/Feature/ImpersonationTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportUnknownStatus\:\:\$mapped_to_slug\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/HistoricalImportServiceTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportUnknownStatus\:\:\$occurrences\.$#'
|
||||
identifier: property.notFound
|
||||
count: 3
|
||||
path: tests/Feature/Import/HistoricalImportServiceTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$service\.$#'
|
||||
identifier: property.notFound
|
||||
@@ -1038,6 +1182,18 @@ parameters:
|
||||
count: 3
|
||||
path: tests/Feature/Import/ImportCompletedNotificationTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportUnknownStatus\:\:\$mapped_to_slug\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/ImportControllerTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportUnknownStatus\:\:\$resolved_at\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/ImportControllerTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$tenant\.$#'
|
||||
identifier: property.notFound
|
||||
@@ -1068,6 +1224,42 @@ parameters:
|
||||
count: 5
|
||||
path: tests/Feature/Import/ImportControllerTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$error_message\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/ImportLeadsJobTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$finished_at\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/ImportLeadsJobTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$rows_added\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/ImportLeadsJobTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$rows_skipped\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/ImportLeadsJobTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$status\.$#'
|
||||
identifier: property.notFound
|
||||
count: 3
|
||||
path: tests/Feature/Import/ImportLeadsJobTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$unknown_statuses_count\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/ImportLeadsJobTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$tenant\.$#'
|
||||
identifier: property.notFound
|
||||
@@ -1080,6 +1272,36 @@ parameters:
|
||||
count: 4
|
||||
path: tests/Feature/Import/ImportLeadsJobTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$dry_run\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/ImportModelsTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$entity_type\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/ImportModelsTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$mapping_config\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/ImportModelsTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportLog\:\:\$status\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/ImportModelsTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property App\\Models\\ImportUnknownStatus\:\:\$status_ru\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: tests/Feature/Import/ImportModelsTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$tenant\.$#'
|
||||
identifier: property.notFound
|
||||
@@ -1209,13 +1431,13 @@ parameters:
|
||||
-
|
||||
message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:actingAs\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
count: 9
|
||||
count: 12
|
||||
path: tests/Feature/Plan5/Projects/ProjectsStoreTest.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:actingAs\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
count: 6
|
||||
count: 8
|
||||
path: tests/Feature/Plan5/Projects/ProjectsUpdateTest.php
|
||||
|
||||
-
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ref, reactive, computed, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import axios from 'axios';
|
||||
import type { Project } from '../../stores/projectsStore';
|
||||
import { useProjectsStore } from '../../stores/projectsStore';
|
||||
import { REGIONS } from '../../constants/regions';
|
||||
import { REGIONS, FEDERAL_DISTRICT_NAMES } from '../../constants/regions';
|
||||
|
||||
const props = defineProps<{ project: Project | null }>();
|
||||
const emit = defineEmits<{ close: []; saved: [] }>();
|
||||
@@ -11,8 +11,7 @@ const emit = defineEmits<{ close: []; saved: [] }>();
|
||||
interface FormState {
|
||||
name: string;
|
||||
daily_limit_target: number;
|
||||
region_mask: number;
|
||||
region_mode: 'include' | 'exclude';
|
||||
regions: number[];
|
||||
delivery_days_mask: number;
|
||||
sms_senders: string[];
|
||||
sms_keyword: string;
|
||||
@@ -21,48 +20,31 @@ interface FormState {
|
||||
const form = reactive<FormState>({
|
||||
name: '',
|
||||
daily_limit_target: 50,
|
||||
region_mask: 0,
|
||||
region_mode: 'include',
|
||||
regions: [],
|
||||
delivery_days_mask: 127,
|
||||
sms_senders: [],
|
||||
sms_keyword: '',
|
||||
});
|
||||
|
||||
const selectedRegions = ref<number[]>([]);
|
||||
const selectableRegions = REGIONS.filter((r) => r.code !== 0);
|
||||
|
||||
function maskToCodes(mask: number): number[] {
|
||||
const codes: number[] = [];
|
||||
for (let i = 1; i <= 31; i++) if (mask & (1 << i)) codes.push(i);
|
||||
return codes;
|
||||
}
|
||||
|
||||
function reseedFromProject(p: Project | null): void {
|
||||
if (!p) return;
|
||||
form.name = p.name;
|
||||
form.daily_limit_target = p.daily_limit_target;
|
||||
form.region_mask = p.region_mask ?? 0;
|
||||
form.region_mode = (p.region_mode ?? 'include') as 'include' | 'exclude';
|
||||
form.regions = Array.isArray(p.regions) ? [...p.regions] : [];
|
||||
form.delivery_days_mask = p.delivery_days_mask ?? 127;
|
||||
form.sms_senders = p.sms_senders ?? [];
|
||||
form.sms_keyword = p.sms_keyword ?? '';
|
||||
selectedRegions.value = maskToCodes(form.region_mask);
|
||||
}
|
||||
reseedFromProject(props.project);
|
||||
|
||||
watch(() => props.project?.id, () => {
|
||||
reseedFromProject(props.project);
|
||||
});
|
||||
|
||||
watch(selectedRegions, (codes) => {
|
||||
if (codes.length === 0) {
|
||||
form.region_mask = 0;
|
||||
form.region_mode = 'include';
|
||||
} else {
|
||||
form.region_mask = codes.reduce((acc, c) => (c >= 1 && c <= 31 ? acc | (1 << c) : acc), 0);
|
||||
form.region_mode = 'exclude';
|
||||
}
|
||||
});
|
||||
watch(
|
||||
() => props.project?.id,
|
||||
() => {
|
||||
reseedFromProject(props.project);
|
||||
},
|
||||
);
|
||||
|
||||
const saving = ref(false);
|
||||
const errors = reactive<Record<string, string[]>>({});
|
||||
@@ -76,7 +58,9 @@ async function onPause(): Promise<void> {
|
||||
|
||||
async function onDelete(): Promise<void> {
|
||||
if (!props.project) return;
|
||||
const ok = window.confirm('Архивировать проект? Действие необратимо в Plan 5 (восстановление потребует ручного запроса).');
|
||||
const ok = window.confirm(
|
||||
'Архивировать проект? Действие необратимо в Plan 5 (восстановление потребует ручного запроса).',
|
||||
);
|
||||
if (!ok) return;
|
||||
await store.archive(props.project.id);
|
||||
emit('close');
|
||||
@@ -90,8 +74,7 @@ async function onSave(): Promise<void> {
|
||||
const payload: Record<string, unknown> = {
|
||||
name: form.name,
|
||||
daily_limit_target: form.daily_limit_target,
|
||||
region_mask: form.region_mask,
|
||||
region_mode: form.region_mode,
|
||||
regions: form.regions,
|
||||
delivery_days_mask: form.delivery_days_mask,
|
||||
};
|
||||
if (props.project.signal_type === 'sms') {
|
||||
@@ -122,7 +105,7 @@ const activeDays = computed<boolean[]>(() => {
|
||||
});
|
||||
|
||||
function toggleDay(i: number): void {
|
||||
form.delivery_days_mask ^= (1 << i);
|
||||
form.delivery_days_mask ^= 1 << i;
|
||||
}
|
||||
|
||||
const dayLabels = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
|
||||
@@ -159,7 +142,7 @@ const dayLabels = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
|
||||
<div class="pdd-field">
|
||||
<span class="pdd-label">Регионы (пусто = вся РФ)</span>
|
||||
<v-autocomplete
|
||||
v-model="selectedRegions"
|
||||
v-model="form.regions"
|
||||
:items="selectableRegions"
|
||||
item-title="name"
|
||||
item-value="code"
|
||||
@@ -169,7 +152,15 @@ const dayLabels = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
|
||||
density="comfortable"
|
||||
hide-details
|
||||
data-testid="pdd-regions"
|
||||
/>
|
||||
>
|
||||
<template #item="{ props: itemProps, item }">
|
||||
<v-list-item v-bind="itemProps">
|
||||
<template #subtitle>
|
||||
{{ FEDERAL_DISTRICT_NAMES[item.raw.federalDistrict] || '' }}
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
</div>
|
||||
|
||||
<div class="pdd-field">
|
||||
@@ -197,13 +188,12 @@ const dayLabels = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
|
||||
<button class="pdd-btn pdd-btn-error" data-testid="pdd-delete" @click="onDelete">🗄 Удалить</button>
|
||||
</div>
|
||||
<div class="pdd-foot-right">
|
||||
<button class="pdd-btn pdd-btn-text" data-testid="pdd-cancel" @click="$emit('close')">Отмена</button>
|
||||
<button
|
||||
class="pdd-btn pdd-btn-primary"
|
||||
data-testid="pdd-save"
|
||||
:disabled="saving"
|
||||
@click="onSave"
|
||||
>Сохранить</button>
|
||||
<button class="pdd-btn pdd-btn-text" data-testid="pdd-cancel" @click="$emit('close')">
|
||||
Отмена
|
||||
</button>
|
||||
<button class="pdd-btn pdd-btn-primary" data-testid="pdd-save" :disabled="saving" @click="onSave">
|
||||
Сохранить
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -212,34 +202,123 @@ const dayLabels = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
|
||||
|
||||
<style scoped>
|
||||
.project-details-drawer {
|
||||
position: fixed; top: 0; right: 0; bottom: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 480px;
|
||||
background: var(--liderra-surface, #ffffff);
|
||||
border-left: 1px solid var(--liderra-line, #e6e2d6);
|
||||
box-shadow: -4px 0 16px rgba(0, 0, 0, 0.06);
|
||||
transform: translateX(100%);
|
||||
transition: transform 240ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||
display: flex; flex-direction: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 5;
|
||||
}
|
||||
.project-details-drawer.open { transform: translateX(0); }
|
||||
.pdd-content { display: flex; flex-direction: column; height: 100%; }
|
||||
.pdd-head { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--liderra-line, #e6e2d6); }
|
||||
.pdd-title { font-weight: 600; font-size: 16px; }
|
||||
.pdd-close { background: none; border: 0; cursor: pointer; font-size: 18px; padding: 4px; }
|
||||
.pdd-body { padding: 16px 20px; display: flex; flex-direction: column; gap: 14px; flex: 1; overflow-y: auto; }
|
||||
.pdd-field { display: flex; flex-direction: column; gap: 4px; }
|
||||
.pdd-label { font-size: 12px; color: #6b6f72; }
|
||||
.pdd-input { padding: 8px 10px; border: 1px solid var(--liderra-line, #e6e2d6); border-radius: 6px; font: inherit; }
|
||||
.pdd-days { display: flex; gap: 4px; }
|
||||
.pdd-day { padding: 6px 10px; border: 1px solid var(--liderra-line, #e6e2d6); background: #ffffff; border-radius: 4px; cursor: pointer; font: inherit; }
|
||||
.pdd-day.active { background: #0f6e56; color: #ffffff; border-color: #0f6e56; }
|
||||
.pdd-foot { display: flex; justify-content: space-between; padding: 12px 20px; border-top: 1px solid var(--liderra-line, #e6e2d6); }
|
||||
.pdd-foot-left, .pdd-foot-right { display: flex; gap: 8px; }
|
||||
.pdd-btn { padding: 6px 14px; border: 0; border-radius: 6px; cursor: pointer; font: inherit; }
|
||||
.pdd-btn-text { background: transparent; color: #081319; }
|
||||
.pdd-btn-primary { background: #0f6e56; color: #ffffff; }
|
||||
.pdd-btn-warning { background: #f59e0b; color: #ffffff; }
|
||||
.pdd-btn-error { background: #dc2626; color: #ffffff; }
|
||||
.pdd-error { color: #dc2626; font-size: 12px; margin-top: 4px; }
|
||||
.project-details-drawer.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.pdd-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.pdd-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--liderra-line, #e6e2d6);
|
||||
}
|
||||
.pdd-title {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
.pdd-close {
|
||||
background: none;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
padding: 4px;
|
||||
}
|
||||
.pdd-body {
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.pdd-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.pdd-label {
|
||||
font-size: 12px;
|
||||
color: #6b6f72;
|
||||
}
|
||||
.pdd-input {
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--liderra-line, #e6e2d6);
|
||||
border-radius: 6px;
|
||||
font: inherit;
|
||||
}
|
||||
.pdd-days {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
.pdd-day {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--liderra-line, #e6e2d6);
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
.pdd-day.active {
|
||||
background: #0f6e56;
|
||||
color: #ffffff;
|
||||
border-color: #0f6e56;
|
||||
}
|
||||
.pdd-foot {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 20px;
|
||||
border-top: 1px solid var(--liderra-line, #e6e2d6);
|
||||
}
|
||||
.pdd-foot-left,
|
||||
.pdd-foot-right {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.pdd-btn {
|
||||
padding: 6px 14px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
.pdd-btn-text {
|
||||
background: transparent;
|
||||
color: #081319;
|
||||
}
|
||||
.pdd-btn-primary {
|
||||
background: #0f6e56;
|
||||
color: #ffffff;
|
||||
}
|
||||
.pdd-btn-warning {
|
||||
background: #f59e0b;
|
||||
color: #ffffff;
|
||||
}
|
||||
.pdd-btn-error {
|
||||
background: #dc2626;
|
||||
color: #ffffff;
|
||||
}
|
||||
.pdd-error {
|
||||
color: #dc2626;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,42 +1,119 @@
|
||||
export interface Region {
|
||||
code: number;
|
||||
name: string;
|
||||
code: number; // 1..89, sequential по конституционному порядку (Art. 65)
|
||||
name: string; // официальное название субъекта
|
||||
federalDistrict: number; // 1..8 (см. FEDERAL_DISTRICT_NAMES)
|
||||
}
|
||||
|
||||
// MVP: 31 региона (коды 1..31) ограничены 32-bit region_mask из Plan 5 Task 9.
|
||||
// Sentinel code:0 = «Вся РФ» (включает все регионы, эквивалент пустой маски).
|
||||
// Имена — официальные субъекты РФ по конституционному порядку нумерации.
|
||||
// Конституционный порядок (ст. 65 Конституции РФ, ред. 2022):
|
||||
// 24 республики (1..24) → 9 краёв (25..33) → 48 областей (34..81) →
|
||||
// 3 города фед.знач. (82..84) → 1 АО Еврейская (85) → 4 АО (86..89).
|
||||
// Sentinel code:0 = "Вся РФ" (UI hint, в БД хранится как regions=[]).
|
||||
export const REGIONS: Region[] = [
|
||||
{ code: 0, name: 'Вся РФ' },
|
||||
{ code: 1, name: 'Республика Адыгея' },
|
||||
{ code: 2, name: 'Республика Башкортостан' },
|
||||
{ code: 3, name: 'Республика Бурятия' },
|
||||
{ code: 4, name: 'Республика Алтай' },
|
||||
{ code: 5, name: 'Республика Дагестан' },
|
||||
{ code: 6, name: 'Республика Ингушетия' },
|
||||
{ code: 7, name: 'Кабардино-Балкарская Республика' },
|
||||
{ code: 8, name: 'Республика Калмыкия' },
|
||||
{ code: 9, name: 'Карачаево-Черкесская Республика' },
|
||||
{ code: 10, name: 'Республика Карелия' },
|
||||
{ code: 11, name: 'Республика Коми' },
|
||||
{ code: 12, name: 'Республика Марий Эл' },
|
||||
{ code: 13, name: 'Республика Мордовия' },
|
||||
{ code: 14, name: 'Республика Саха (Якутия)' },
|
||||
{ code: 15, name: 'Республика Северная Осетия — Алания' },
|
||||
{ code: 16, name: 'Республика Татарстан' },
|
||||
{ code: 17, name: 'Республика Тыва' },
|
||||
{ code: 18, name: 'Удмуртская Республика' },
|
||||
{ code: 19, name: 'Республика Хакасия' },
|
||||
{ code: 20, name: 'Чеченская Республика' },
|
||||
{ code: 21, name: 'Чувашская Республика' },
|
||||
{ code: 22, name: 'Алтайский край' },
|
||||
{ code: 23, name: 'Краснодарский край' },
|
||||
{ code: 24, name: 'Красноярский край' },
|
||||
{ code: 25, name: 'Приморский край' },
|
||||
{ code: 26, name: 'Ставропольский край' },
|
||||
{ code: 27, name: 'Хабаровский край' },
|
||||
{ code: 28, name: 'Амурская область' },
|
||||
{ code: 29, name: 'Архангельская область' },
|
||||
{ code: 30, name: 'Астраханская область' },
|
||||
{ code: 31, name: 'Белгородская область' },
|
||||
{ code: 0, name: 'Вся РФ', federalDistrict: 0 },
|
||||
// 24 республики
|
||||
{ code: 1, name: 'Республика Адыгея', federalDistrict: 3 },
|
||||
{ code: 2, name: 'Республика Алтай', federalDistrict: 7 },
|
||||
{ code: 3, name: 'Республика Башкортостан', federalDistrict: 5 },
|
||||
{ code: 4, name: 'Республика Бурятия', federalDistrict: 8 },
|
||||
{ code: 5, name: 'Республика Дагестан', federalDistrict: 4 },
|
||||
{ code: 6, name: 'Донецкая Народная Республика', federalDistrict: 3 },
|
||||
{ code: 7, name: 'Республика Ингушетия', federalDistrict: 4 },
|
||||
{ code: 8, name: 'Кабардино-Балкарская Республика', federalDistrict: 4 },
|
||||
{ code: 9, name: 'Республика Калмыкия', federalDistrict: 3 },
|
||||
{ code: 10, name: 'Карачаево-Черкесская Республика', federalDistrict: 4 },
|
||||
{ code: 11, name: 'Республика Карелия', federalDistrict: 2 },
|
||||
{ code: 12, name: 'Республика Коми', federalDistrict: 2 },
|
||||
{ code: 13, name: 'Республика Крым', federalDistrict: 3 },
|
||||
{ code: 14, name: 'Луганская Народная Республика', federalDistrict: 3 },
|
||||
{ code: 15, name: 'Республика Марий Эл', federalDistrict: 5 },
|
||||
{ code: 16, name: 'Республика Мордовия', federalDistrict: 5 },
|
||||
{ code: 17, name: 'Республика Саха (Якутия)', federalDistrict: 8 },
|
||||
{ code: 18, name: 'Республика Северная Осетия — Алания', federalDistrict: 4 },
|
||||
{ code: 19, name: 'Республика Татарстан', federalDistrict: 5 },
|
||||
{ code: 20, name: 'Республика Тыва', federalDistrict: 7 },
|
||||
{ code: 21, name: 'Удмуртская Республика', federalDistrict: 5 },
|
||||
{ code: 22, name: 'Республика Хакасия', federalDistrict: 7 },
|
||||
{ code: 23, name: 'Чеченская Республика', federalDistrict: 4 },
|
||||
{ code: 24, name: 'Чувашская Республика', federalDistrict: 5 },
|
||||
// 9 краёв
|
||||
{ code: 25, name: 'Алтайский край', federalDistrict: 7 },
|
||||
{ code: 26, name: 'Забайкальский край', federalDistrict: 8 },
|
||||
{ code: 27, name: 'Камчатский край', federalDistrict: 8 },
|
||||
{ code: 28, name: 'Краснодарский край', federalDistrict: 3 },
|
||||
{ code: 29, name: 'Красноярский край', federalDistrict: 7 },
|
||||
{ code: 30, name: 'Пермский край', federalDistrict: 5 },
|
||||
{ code: 31, name: 'Приморский край', federalDistrict: 8 },
|
||||
{ code: 32, name: 'Ставропольский край', federalDistrict: 4 },
|
||||
{ code: 33, name: 'Хабаровский край', federalDistrict: 8 },
|
||||
// 48 областей
|
||||
{ code: 34, name: 'Амурская область', federalDistrict: 8 },
|
||||
{ code: 35, name: 'Архангельская область', federalDistrict: 2 },
|
||||
{ code: 36, name: 'Астраханская область', federalDistrict: 3 },
|
||||
{ code: 37, name: 'Белгородская область', federalDistrict: 1 },
|
||||
{ code: 38, name: 'Брянская область', federalDistrict: 1 },
|
||||
{ code: 39, name: 'Владимирская область', federalDistrict: 1 },
|
||||
{ code: 40, name: 'Волгоградская область', federalDistrict: 3 },
|
||||
{ code: 41, name: 'Вологодская область', federalDistrict: 2 },
|
||||
{ code: 42, name: 'Воронежская область', federalDistrict: 1 },
|
||||
{ code: 43, name: 'Запорожская область', federalDistrict: 3 },
|
||||
{ code: 44, name: 'Ивановская область', federalDistrict: 1 },
|
||||
{ code: 45, name: 'Иркутская область', federalDistrict: 7 },
|
||||
{ code: 46, name: 'Калининградская область', federalDistrict: 2 },
|
||||
{ code: 47, name: 'Калужская область', federalDistrict: 1 },
|
||||
{ code: 48, name: 'Кемеровская область', federalDistrict: 7 },
|
||||
{ code: 49, name: 'Кировская область', federalDistrict: 5 },
|
||||
{ code: 50, name: 'Костромская область', federalDistrict: 1 },
|
||||
{ code: 51, name: 'Курганская область', federalDistrict: 6 },
|
||||
{ code: 52, name: 'Курская область', federalDistrict: 1 },
|
||||
{ code: 53, name: 'Ленинградская область', federalDistrict: 2 },
|
||||
{ code: 54, name: 'Липецкая область', federalDistrict: 1 },
|
||||
{ code: 55, name: 'Магаданская область', federalDistrict: 8 },
|
||||
{ code: 56, name: 'Московская область', federalDistrict: 1 },
|
||||
{ code: 57, name: 'Мурманская область', federalDistrict: 2 },
|
||||
{ code: 58, name: 'Нижегородская область', federalDistrict: 5 },
|
||||
{ code: 59, name: 'Новгородская область', federalDistrict: 2 },
|
||||
{ code: 60, name: 'Новосибирская область', federalDistrict: 7 },
|
||||
{ code: 61, name: 'Омская область', federalDistrict: 7 },
|
||||
{ code: 62, name: 'Оренбургская область', federalDistrict: 5 },
|
||||
{ code: 63, name: 'Орловская область', federalDistrict: 1 },
|
||||
{ code: 64, name: 'Пензенская область', federalDistrict: 5 },
|
||||
{ code: 65, name: 'Псковская область', federalDistrict: 2 },
|
||||
{ code: 66, name: 'Ростовская область', federalDistrict: 3 },
|
||||
{ code: 67, name: 'Рязанская область', federalDistrict: 1 },
|
||||
{ code: 68, name: 'Самарская область', federalDistrict: 5 },
|
||||
{ code: 69, name: 'Саратовская область', federalDistrict: 5 },
|
||||
{ code: 70, name: 'Сахалинская область', federalDistrict: 8 },
|
||||
{ code: 71, name: 'Свердловская область', federalDistrict: 6 },
|
||||
{ code: 72, name: 'Смоленская область', federalDistrict: 1 },
|
||||
{ code: 73, name: 'Тамбовская область', federalDistrict: 1 },
|
||||
{ code: 74, name: 'Тверская область', federalDistrict: 1 },
|
||||
{ code: 75, name: 'Томская область', federalDistrict: 7 },
|
||||
{ code: 76, name: 'Тульская область', federalDistrict: 1 },
|
||||
{ code: 77, name: 'Тюменская область', federalDistrict: 6 },
|
||||
{ code: 78, name: 'Ульяновская область', federalDistrict: 5 },
|
||||
{ code: 79, name: 'Херсонская область', federalDistrict: 3 },
|
||||
{ code: 80, name: 'Челябинская область', federalDistrict: 6 },
|
||||
{ code: 81, name: 'Ярославская область', federalDistrict: 1 },
|
||||
// 3 города федерального значения
|
||||
{ code: 82, name: 'Москва', federalDistrict: 1 },
|
||||
{ code: 83, name: 'Санкт-Петербург', federalDistrict: 2 },
|
||||
{ code: 84, name: 'Севастополь', federalDistrict: 3 },
|
||||
// 1 автономная область
|
||||
{ code: 85, name: 'Еврейская автономная область', federalDistrict: 8 },
|
||||
// 4 автономных округа
|
||||
{ code: 86, name: 'Ненецкий автономный округ', federalDistrict: 2 },
|
||||
{ code: 87, name: 'Ханты-Мансийский автономный округ — Югра', federalDistrict: 6 },
|
||||
{ code: 88, name: 'Чукотский автономный округ', federalDistrict: 8 },
|
||||
{ code: 89, name: 'Ямало-Ненецкий автономный округ', federalDistrict: 6 },
|
||||
];
|
||||
|
||||
export const FEDERAL_DISTRICT_NAMES: Record<number, string> = {
|
||||
1: 'Центральный',
|
||||
2: 'Северо-Западный',
|
||||
3: 'Южный',
|
||||
4: 'Северо-Кавказский',
|
||||
5: 'Приволжский',
|
||||
6: 'Уральский',
|
||||
7: 'Сибирский',
|
||||
8: 'Дальневосточный',
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface Project {
|
||||
archived_at: string | null;
|
||||
region_mask?: number;
|
||||
region_mode?: string;
|
||||
regions?: number[]; // Plan 6 — subject codes 1..89; пустой массив = вся РФ
|
||||
delivery_days_mask?: number;
|
||||
sync_status: 'ok' | 'pending' | 'failed';
|
||||
last_synced_at?: string | null;
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Settings — настройки тенанта/пользователя. 8 вкладок (по v8.5 §13 + ТЗ §14).
|
||||
* Settings — настройки тенанта/пользователя. 4 рабочие вкладки.
|
||||
*
|
||||
* Источник дизайна: liderra_v8_handoff/concepts/v8_settings.html.
|
||||
* Полностью реализованы (с UI-разводкой): Профиль, Безопасность, API и Webhook,
|
||||
* Уведомления (матрица 8×3 по schema v8.7 §4 users.notification_preferences).
|
||||
* Placeholder-заглушки: Проекты, Команда, Интеграции, Тихие часы.
|
||||
*
|
||||
* Аудит D6/D7 (Sprint 3E, 2026-05-16): placeholder-вкладки Проекты/Команда/
|
||||
* Интеграции/Тихие часы убраны — UI не должен обещать «в разработке».
|
||||
* «Проекты» дублировали /projects; «Команда» и «Тихие часы» (ТЗ §17.8)
|
||||
* требуют schema+backend (отдельные эпики); «Интеграции» внешне-блокированы (Б-1).
|
||||
* Вкладки вернутся при реальной реализации соответствующих модулей.
|
||||
*/
|
||||
import { computed, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import ApiTab from './settings/ApiTab.vue';
|
||||
import NotificationsTab from './settings/NotificationsTab.vue';
|
||||
import PlaceholderTab from './settings/PlaceholderTab.vue';
|
||||
import ProfileTab from './settings/ProfileTab.vue';
|
||||
import SecurityTab from './settings/SecurityTab.vue';
|
||||
|
||||
@@ -23,41 +27,11 @@ interface Tab {
|
||||
const tabs: Tab[] = [
|
||||
{ id: 'profile', label: 'Профиль', icon: 'mdi-account-outline' },
|
||||
{ id: 'security', label: 'Безопасность', icon: 'mdi-shield-lock-outline' },
|
||||
{ id: 'projects', label: 'Проекты', icon: 'mdi-folder-outline' },
|
||||
{ id: 'team', label: 'Команда', icon: 'mdi-account-group-outline' },
|
||||
{ id: 'api', label: 'API и Webhook', icon: 'mdi-api' },
|
||||
{ id: 'integrations', label: 'Интеграции', icon: 'mdi-puzzle-outline' },
|
||||
{ id: 'hours', label: 'Тихие часы', icon: 'mdi-clock-outline' },
|
||||
{ id: 'notifications', label: 'Уведомления', icon: 'mdi-bell-outline' },
|
||||
];
|
||||
|
||||
const activeTab = ref('profile');
|
||||
|
||||
const placeholderProps = computed(() => {
|
||||
const map: Record<string, { title: string; description: string }> = {
|
||||
projects: {
|
||||
title: 'Проекты',
|
||||
description:
|
||||
'Управление проектами тенанта (макс. 10 на тарифе «Команда»). Для каждого проекта — поставщик ГЦК, цена за лид, активные UTM-кампании.',
|
||||
},
|
||||
team: {
|
||||
title: 'Команда',
|
||||
description:
|
||||
'Менеджеры тенанта (макс. 4 + расширение). Назначение прав, автораспределение, ограничение доступа к проектам.',
|
||||
},
|
||||
integrations: {
|
||||
title: 'Интеграции',
|
||||
description:
|
||||
'Подключение Telegram-бота для нотификаций, экспорт в 1С 8.3, JivoSite helpdesk, Yandex 360 SSO.',
|
||||
},
|
||||
hours: {
|
||||
title: 'Тихие часы',
|
||||
description:
|
||||
'Расписание, в которое не приходят SMS/звонки автонапоминаний (например, 22:00-08:00 + выходные).',
|
||||
},
|
||||
};
|
||||
return map[activeTab.value];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -91,11 +65,6 @@ const placeholderProps = computed(() => {
|
||||
<SecurityTab v-else-if="activeTab === 'security'" />
|
||||
<ApiTab v-else-if="activeTab === 'api'" />
|
||||
<NotificationsTab v-else-if="activeTab === 'notifications'" />
|
||||
<PlaceholderTab
|
||||
v-else-if="placeholderProps"
|
||||
:title="placeholderProps.title"
|
||||
:description="placeholderProps.description"
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -76,12 +76,34 @@
|
||||
:error-messages="errors.daily_limit_target"
|
||||
/>
|
||||
|
||||
<v-autocomplete
|
||||
v-model="form.regions"
|
||||
:items="selectableRegions"
|
||||
item-title="name"
|
||||
item-value="code"
|
||||
label="Регионы (пусто = вся РФ)"
|
||||
multiple
|
||||
chips
|
||||
clearable
|
||||
density="comfortable"
|
||||
class="ld-input-quiet"
|
||||
data-testid="regions-autocomplete"
|
||||
>
|
||||
<template #item="{ props: itemProps, item }">
|
||||
<v-list-item v-bind="itemProps">
|
||||
<template #subtitle>
|
||||
{{ FEDERAL_DISTRICT_NAMES[item.raw.federalDistrict] || '' }}
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
|
||||
<v-alert
|
||||
v-if="generalError"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
class="mb-3"
|
||||
class="mt-3"
|
||||
closable
|
||||
@click:close="generalError = null"
|
||||
>
|
||||
@@ -114,9 +136,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { apiClient, ensureCsrfCookie, extractErrorMessage } from '../../api/client';
|
||||
import { REGIONS, FEDERAL_DISTRICT_NAMES } from '../../constants/regions';
|
||||
import type { Project } from '../../stores/projectsStore';
|
||||
import DevIndexBadge from '../../components/DevIndexBadge.vue';
|
||||
|
||||
const selectableRegions = REGIONS.filter((r) => r.code !== 0);
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
mode?: 'create' | 'edit';
|
||||
@@ -124,9 +149,8 @@ const props = defineProps<{
|
||||
}>();
|
||||
const emit = defineEmits(['update:modelValue', 'saved']);
|
||||
|
||||
// region_mask=255 = все 8 ФО (schema default, см. db/schema.sql §projects).
|
||||
// PDD regions UI отключён до закрытия Plan 6 — конфликт с 8-битной ФО-маской
|
||||
// в PhonePrefixService.php (1 phone prefix ↔ 1 ФО, не субъект).
|
||||
// Plan 6: regions = subject codes (1..89) — backend dual-writes region_mask/region_mode.
|
||||
// Пустой массив = вся РФ.
|
||||
const form = reactive({
|
||||
name: '',
|
||||
signal_type: 'site' as 'site' | 'call' | 'sms',
|
||||
@@ -134,8 +158,7 @@ const form = reactive({
|
||||
sms_senders: [] as string[],
|
||||
sms_keyword: '',
|
||||
daily_limit_target: 50,
|
||||
region_mask: 255,
|
||||
region_mode: 'include' as 'include' | 'exclude',
|
||||
regions: [] as number[],
|
||||
delivery_days_mask: 127,
|
||||
});
|
||||
const errors = reactive<Record<string, string[]>>({});
|
||||
@@ -159,6 +182,7 @@ watch(
|
||||
if (open) generalError.value = null;
|
||||
if (open && props.mode === 'edit' && props.project) {
|
||||
Object.assign(form, props.project);
|
||||
form.regions = Array.isArray(props.project.regions) ? [...props.project.regions] : [];
|
||||
const days: number[] = [];
|
||||
for (let i = 0; i < 7; i++) if (form.delivery_days_mask & (1 << i)) days.push(i);
|
||||
selectedDays.value = days;
|
||||
@@ -170,8 +194,7 @@ watch(
|
||||
sms_senders: [],
|
||||
sms_keyword: '',
|
||||
daily_limit_target: 50,
|
||||
region_mask: 255,
|
||||
region_mode: 'include',
|
||||
regions: [],
|
||||
delivery_days_mask: 127,
|
||||
});
|
||||
selectedDays.value = [0, 1, 2, 3, 4, 5, 6];
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Универсальный placeholder для ещё-не-реализованных вкладок Settings.
|
||||
* Используется для вкладок: Проекты, Команда, Интеграции, Тихие часы.
|
||||
*
|
||||
* При реализации каждой вкладки — заменяется на отдельный component.
|
||||
*/
|
||||
defineProps<{ title: string; description: string }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tab-content">
|
||||
<h2 class="tab-title text-h6 mb-3">{{ title }}</h2>
|
||||
<v-alert type="info" variant="tonal" density="compact" class="mb-4">
|
||||
<strong>В разработке.</strong> Этот раздел реализуется в следующих коммитах.
|
||||
</v-alert>
|
||||
<p class="text-body-2 text-medium-emphasis">{{ description }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tab-title {
|
||||
font-variation-settings: 'opsz' 18;
|
||||
letter-spacing: -0.005em;
|
||||
}
|
||||
</style>
|
||||
@@ -59,11 +59,12 @@ it('supplier_csv_reconcile_log table exists with required columns and status CHE
|
||||
]))->toThrow(QueryException::class);
|
||||
});
|
||||
|
||||
it('schema.sql v8.21 has correct metrics — 63 base tables, 118 indexes, 40 RLS policies', function () {
|
||||
it('schema.sql v8.22 has correct metrics — 63 base tables, 119 indexes, 40 RLS policies', function () {
|
||||
// Замена destructive `migrate:fresh` (cross-test coupling: после DROP CASCADE остальные
|
||||
// Feature-тесты в той же сессии видели пустую БД). Static parse `db/schema.sql` —
|
||||
// источник истины метрик из spec §2.4 / db/CHANGELOG_schema.md v8.21.
|
||||
// источник истины метрик из spec §2.4 / db/CHANGELOG_schema.md v8.22.
|
||||
// v8.21 (Sprint 4): +1 таблица import_unknown_statuses, +1 индекс, +1 RLS-политика.
|
||||
// v8.22 (Plan 6/C9): +1 GIN-индекс idx_projects_regions.
|
||||
$schemaPath = dirname(base_path()).DIRECTORY_SEPARATOR.'db'.DIRECTORY_SEPARATOR.'schema.sql';
|
||||
expect(is_file($schemaPath) && is_readable($schemaPath))->toBeTrue();
|
||||
$schema = file_get_contents($schemaPath);
|
||||
@@ -76,7 +77,7 @@ it('schema.sql v8.21 has correct metrics — 63 base tables, 118 indexes, 40 RLS
|
||||
expect($baseTables)->toBe(63);
|
||||
|
||||
$createIndexes = preg_match_all('/^CREATE\s+(?:UNIQUE\s+)?INDEX\b/m', $schema);
|
||||
expect($createIndexes)->toBe(118);
|
||||
expect($createIndexes)->toBe(119); // v8.22 (Plan 6/C9): +1 GIN idx_projects_regions
|
||||
|
||||
$createPolicies = preg_match_all('/^CREATE\s+POLICY\b/m', $schema);
|
||||
expect($createPolicies)->toBe(40);
|
||||
|
||||
@@ -19,8 +19,7 @@ it('creates a site project with valid payload', function () {
|
||||
'signal_type' => 'site',
|
||||
'signal_identifier' => 'okna-spb.ru',
|
||||
'daily_limit_target' => 50,
|
||||
'region_mask' => 0,
|
||||
'region_mode' => 'include',
|
||||
'regions' => [],
|
||||
'delivery_days_mask' => 127,
|
||||
]);
|
||||
|
||||
@@ -36,7 +35,7 @@ it('rejects invalid site domain', function () {
|
||||
|
||||
$response = $this->actingAs($user)->postJson('/api/projects', [
|
||||
'name' => 'X', 'signal_type' => 'site', 'signal_identifier' => 'not a domain',
|
||||
'daily_limit_target' => 50, 'region_mask' => 0, 'region_mode' => 'include',
|
||||
'daily_limit_target' => 50, 'regions' => [],
|
||||
'delivery_days_mask' => 127,
|
||||
]);
|
||||
|
||||
@@ -50,7 +49,7 @@ it('creates a call project with valid 11-digit phone', function () {
|
||||
|
||||
$response = $this->actingAs($user)->postJson('/api/projects', [
|
||||
'name' => 'Натяжные', 'signal_type' => 'call', 'signal_identifier' => '79161234567',
|
||||
'daily_limit_target' => 30, 'region_mask' => 0, 'region_mode' => 'include',
|
||||
'daily_limit_target' => 30, 'regions' => [],
|
||||
'delivery_days_mask' => 127,
|
||||
]);
|
||||
|
||||
@@ -63,7 +62,7 @@ it('rejects call signal_identifier not starting with 7', function () {
|
||||
|
||||
$response = $this->actingAs($user)->postJson('/api/projects', [
|
||||
'name' => 'X', 'signal_type' => 'call', 'signal_identifier' => '89991234567',
|
||||
'daily_limit_target' => 30, 'region_mask' => 0, 'region_mode' => 'include',
|
||||
'daily_limit_target' => 30, 'regions' => [],
|
||||
'delivery_days_mask' => 127,
|
||||
]);
|
||||
|
||||
@@ -77,7 +76,7 @@ it('creates sms project with senders + keyword', function () {
|
||||
$response = $this->actingAs($user)->postJson('/api/projects', [
|
||||
'name' => 'Ипотека', 'signal_type' => 'sms',
|
||||
'sms_senders' => ['TINKOFF'], 'sms_keyword' => 'ипотека',
|
||||
'daily_limit_target' => 100, 'region_mask' => 0, 'region_mode' => 'include',
|
||||
'daily_limit_target' => 100, 'regions' => [],
|
||||
'delivery_days_mask' => 127,
|
||||
]);
|
||||
|
||||
@@ -93,7 +92,7 @@ it('rejects sms project without sms_senders', function () {
|
||||
|
||||
$response = $this->actingAs($user)->postJson('/api/projects', [
|
||||
'name' => 'X', 'signal_type' => 'sms',
|
||||
'daily_limit_target' => 100, 'region_mask' => 0, 'region_mode' => 'include',
|
||||
'daily_limit_target' => 100, 'regions' => [],
|
||||
'delivery_days_mask' => 127,
|
||||
]);
|
||||
|
||||
@@ -108,7 +107,7 @@ it('rejects when tenant exceeds max_projects limit', function () {
|
||||
|
||||
$response = $this->actingAs($user)->postJson('/api/projects', [
|
||||
'name' => 'second', 'signal_type' => 'site', 'signal_identifier' => 'second.ru',
|
||||
'daily_limit_target' => 10, 'region_mask' => 0, 'region_mode' => 'include',
|
||||
'daily_limit_target' => 10, 'regions' => [],
|
||||
'delivery_days_mask' => 127,
|
||||
]);
|
||||
|
||||
@@ -123,7 +122,7 @@ it('forces tenant_id from auth user (not from payload)', function () {
|
||||
$this->actingAs($userA)->postJson('/api/projects', [
|
||||
'tenant_id' => $tenantB->id, // попытка инъекции
|
||||
'name' => 'X', 'signal_type' => 'site', 'signal_identifier' => 'x.ru',
|
||||
'daily_limit_target' => 10, 'region_mask' => 0, 'region_mode' => 'include',
|
||||
'daily_limit_target' => 10, 'regions' => [],
|
||||
'delivery_days_mask' => 127,
|
||||
]);
|
||||
|
||||
@@ -137,10 +136,67 @@ it('rejects site domain with consecutive dots', function () {
|
||||
|
||||
$response = $this->actingAs($user)->postJson('/api/projects', [
|
||||
'name' => 'X', 'signal_type' => 'site', 'signal_identifier' => 'okna..spb.ru',
|
||||
'daily_limit_target' => 50, 'region_mask' => 0, 'region_mode' => 'include',
|
||||
'daily_limit_target' => 50, 'regions' => [],
|
||||
'delivery_days_mask' => 127,
|
||||
]);
|
||||
|
||||
$response->assertStatus(422);
|
||||
$response->assertJsonValidationErrors(['signal_identifier']);
|
||||
});
|
||||
|
||||
// Plan 6 — subject-level regions[] support.
|
||||
|
||||
it('creates project with subject-level regions array', function () {
|
||||
$tenant = Tenant::factory()->create();
|
||||
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
||||
|
||||
$response = $this->actingAs($user)->postJson('/api/projects', [
|
||||
'name' => 'Regions Test Project',
|
||||
'signal_type' => 'site',
|
||||
'signal_identifier' => 'regions-test.example',
|
||||
'daily_limit_target' => 50,
|
||||
'delivery_days_mask' => 127,
|
||||
'regions' => [82, 83], // Москва + СПб
|
||||
]);
|
||||
|
||||
$response->assertStatus(201);
|
||||
$response->assertJsonPath('data.regions', [82, 83]);
|
||||
$created = Project::where('name', 'Regions Test Project')->firstOrFail();
|
||||
expect($created->regions)->toBe([82, 83]);
|
||||
});
|
||||
|
||||
it('dual-writes region_mask=255 + region_mode=include for backward-compat', function () {
|
||||
$tenant = Tenant::factory()->create();
|
||||
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
||||
|
||||
$response = $this->actingAs($user)->postJson('/api/projects', [
|
||||
'name' => 'Dual Write Test',
|
||||
'signal_type' => 'site',
|
||||
'signal_identifier' => 'dualwrite.example',
|
||||
'daily_limit_target' => 50,
|
||||
'delivery_days_mask' => 127,
|
||||
'regions' => [77],
|
||||
]);
|
||||
|
||||
$response->assertStatus(201);
|
||||
$created = Project::where('name', 'Dual Write Test')->firstOrFail();
|
||||
expect($created->region_mask)->toBe(255);
|
||||
expect($created->region_mode)->toBe('include');
|
||||
});
|
||||
|
||||
it('rejects regions code out of 1..89 range with 422', function () {
|
||||
$tenant = Tenant::factory()->create();
|
||||
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
||||
|
||||
$response = $this->actingAs($user)->postJson('/api/projects', [
|
||||
'name' => 'Invalid Code Test',
|
||||
'signal_type' => 'site',
|
||||
'signal_identifier' => 'invalid.example',
|
||||
'daily_limit_target' => 50,
|
||||
'delivery_days_mask' => 127,
|
||||
'regions' => [90, 100],
|
||||
]);
|
||||
|
||||
$response->assertStatus(422);
|
||||
$response->assertJsonValidationErrors(['regions.0', 'regions.1']);
|
||||
});
|
||||
|
||||
@@ -78,14 +78,50 @@ it('cross-tenant update returns 404', function () {
|
||||
])->assertStatus(404);
|
||||
});
|
||||
|
||||
it('updates region_mask and delivery_days_mask', function () {
|
||||
it('updates delivery_days_mask (region_mask now read-only — see regions[] tests below)', function () {
|
||||
// Plan 6: region_mask/region_mode больше не клиент-controllable через UpdateProjectRequest
|
||||
// (validation rules удалены, ProjectService::create dual-writes 255/include).
|
||||
// Источник истины для региональной фильтрации — projects.regions INT[] (Plan 6).
|
||||
// Этот тест адаптирован: проверяет, что delivery_days_mask остаётся writeable через PATCH.
|
||||
$tenant = Tenant::factory()->create();
|
||||
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
||||
$project = Project::factory()->create(['tenant_id' => $tenant->id]);
|
||||
|
||||
$this->actingAs($user)->patchJson("/api/projects/{$project->id}", [
|
||||
'region_mask' => 78, 'region_mode' => 'exclude', 'delivery_days_mask' => 31,
|
||||
'delivery_days_mask' => 31,
|
||||
])->assertOk();
|
||||
|
||||
expect($project->fresh()->region_mask)->toBe(78);
|
||||
expect($project->fresh()->delivery_days_mask)->toBe(31);
|
||||
});
|
||||
|
||||
// Plan 6 — subject-level regions[] support.
|
||||
|
||||
it('updates regions array via PATCH', function () {
|
||||
$tenant = Tenant::factory()->create();
|
||||
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
||||
$project = Project::factory()->create(['tenant_id' => $tenant->id, 'regions' => []]);
|
||||
|
||||
$response = $this->actingAs($user)->patchJson("/api/projects/{$project->id}", [
|
||||
'regions' => [82],
|
||||
]);
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonPath('data.regions', [82]);
|
||||
expect($project->fresh()->regions)->toBe([82]);
|
||||
});
|
||||
|
||||
it('preserves regions when PATCH omits the field (sometimes rule)', function () {
|
||||
$tenant = Tenant::factory()->create();
|
||||
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
||||
$project = Project::factory()->create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'regions' => [82, 83],
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($user)->patchJson("/api/projects/{$project->id}", [
|
||||
'name' => 'Renamed Project',
|
||||
]);
|
||||
|
||||
$response->assertStatus(200);
|
||||
expect($project->fresh()->regions)->toBe([82, 83]);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Models\SupplierProject;
|
||||
use App\Models\SupplierSyncLog;
|
||||
use App\Models\Tenant;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -309,6 +310,36 @@ test('respects time budget by stopping at 20:55 МСК', function (): void {
|
||||
Http::assertNothingSent();
|
||||
});
|
||||
|
||||
test('passes regions directly to allocator without bitmask conversion', function (): void {
|
||||
$tenant = Tenant::factory()->create();
|
||||
Project::factory()->create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'regions' => [82, 83],
|
||||
'region_mask' => 255,
|
||||
]);
|
||||
|
||||
$job = new SyncSupplierProjectsJob;
|
||||
$projects = Project::where('tenant_id', $tenant->id)->get();
|
||||
$adapted = (fn (Collection $p) => $this->adaptProjectsForAllocator($p))->call($job, $projects);
|
||||
|
||||
expect($adapted->first()->regions)->toBe([82, 83]);
|
||||
});
|
||||
|
||||
test('passes empty array to allocator when project has regions=[]', function (): void {
|
||||
$tenant = Tenant::factory()->create();
|
||||
Project::factory()->create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'regions' => [],
|
||||
'region_mask' => 255,
|
||||
]);
|
||||
|
||||
$job = new SyncSupplierProjectsJob;
|
||||
$projects = Project::where('tenant_id', $tenant->id)->get();
|
||||
$adapted = (fn (Collection $p) => $this->adaptProjectsForAllocator($p))->call($job, $projects);
|
||||
|
||||
expect($adapted->first()->regions)->toBe([]);
|
||||
});
|
||||
|
||||
test('sticky auth error throws and sends critical alert email', function (): void {
|
||||
Mail::fake();
|
||||
Bus::fake([RefreshSupplierSessionJob::class]);
|
||||
@@ -346,3 +377,38 @@ test('sticky auth error throws and sends critical alert email', function (): voi
|
||||
return $mail->alertType === 'sticky_auth';
|
||||
});
|
||||
});
|
||||
|
||||
test('outbound: copies project regions[] into supplier_project current_regions via full handle()', function (): void {
|
||||
$tenant = Tenant::factory()->create();
|
||||
$sp = SupplierProject::factory()->create([
|
||||
'platform' => 'B1',
|
||||
'signal_type' => 'site',
|
||||
'unique_key' => 'regions-flow.example.com',
|
||||
'supplier_external_id' => null,
|
||||
'current_limit' => 0,
|
||||
'current_workdays' => [],
|
||||
'current_regions' => [],
|
||||
]);
|
||||
Project::factory()->create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'is_active' => true,
|
||||
'signal_type' => 'site',
|
||||
'signal_identifier' => 'regions-flow.example.com',
|
||||
'supplier_b1_project_id' => $sp->id,
|
||||
'daily_limit_target' => 9,
|
||||
'delivery_days_mask' => 127,
|
||||
'regions' => [82, 83],
|
||||
'region_mask' => 255,
|
||||
'region_mode' => 'include',
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'crm.bp-gr.ru/admin/rt-project-save' => Http::response(['id' => 556], 200),
|
||||
]);
|
||||
|
||||
(new SyncSupplierProjectsJob)->handle();
|
||||
|
||||
$sp->refresh();
|
||||
expect($sp->current_regions)->toBe([82, 83])
|
||||
->and($sp->supplier_external_id)->toBe('556');
|
||||
});
|
||||
|
||||
@@ -6,6 +6,16 @@ import axios from 'axios';
|
||||
|
||||
vi.mock('axios');
|
||||
|
||||
vi.mock('../../resources/js/api/client', () => ({
|
||||
apiClient: {
|
||||
post: vi.fn().mockResolvedValue({ data: {} }),
|
||||
patch: vi.fn().mockResolvedValue({ data: {} }),
|
||||
},
|
||||
ensureCsrfCookie: vi.fn().mockResolvedValue(undefined),
|
||||
extractErrorMessage: vi.fn(() => 'Произошла ошибка.'),
|
||||
}));
|
||||
|
||||
import { apiClient } from '../../resources/js/api/client';
|
||||
import NewProjectDialog from '../../resources/js/views/projects/NewProjectDialog.vue';
|
||||
import type { Project } from '../../resources/js/stores/projectsStore';
|
||||
|
||||
@@ -74,4 +84,24 @@ describe('NewProjectDialog', () => {
|
||||
it.skip('emits saved event after successful POST', async () => {
|
||||
// TODO: см. предыдущий skip — те же причины.
|
||||
});
|
||||
|
||||
it('renders regions autocomplete with 89 selectable subjects (excluding "Вся РФ" sentinel)', async () => {
|
||||
const wrapper = factory();
|
||||
await flushPromises();
|
||||
const autocomplete = wrapper.findComponent({ name: 'VAutocomplete' });
|
||||
expect(autocomplete.exists()).toBe(true);
|
||||
expect(autocomplete.props('items')).toHaveLength(89);
|
||||
expect((autocomplete.props('items') as Array<{ code: number }>).every((r) => r.code !== 0)).toBe(true);
|
||||
});
|
||||
|
||||
it('sends regions array in POST payload', async () => {
|
||||
const wrapper = factory();
|
||||
await flushPromises();
|
||||
const autocomplete = wrapper.findComponent({ name: 'VAutocomplete' });
|
||||
autocomplete.vm.$emit('update:model-value', [82, 83]);
|
||||
await flushPromises();
|
||||
await wrapper.find('[data-testid="submit-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/api/projects', expect.objectContaining({ regions: [82, 83] }));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ const sampleProject: Project = {
|
||||
archived_at: null,
|
||||
region_mask: 0,
|
||||
region_mode: 'include',
|
||||
regions: [],
|
||||
delivery_days_mask: 31, // Mon-Fri
|
||||
sync_status: 'pending',
|
||||
};
|
||||
@@ -50,7 +51,7 @@ describe('ProjectDetailsDrawer', () => {
|
||||
// Days mask 31 = bits 0..4 = Mon..Fri (5 days active)
|
||||
const dayBtns = wrapper.findAll('button[data-testid^="pdd-day-"]');
|
||||
expect(dayBtns.length).toBe(7);
|
||||
const activeBtns = dayBtns.filter(b => b.classes().includes('active'));
|
||||
const activeBtns = dayBtns.filter((b) => b.classes().includes('active'));
|
||||
expect(activeBtns.length).toBe(5);
|
||||
});
|
||||
|
||||
@@ -126,8 +127,12 @@ describe('ProjectDetailsDrawer', () => {
|
||||
});
|
||||
|
||||
it('Pause button calls store.toggleActive', async () => {
|
||||
(axios.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ data: { data: { ...sampleProject, is_active: false } } });
|
||||
(axios.get as unknown as ReturnType<typeof vi.fn> | undefined)?.mockResolvedValue?.({ data: { data: [], meta: { total: 0 } } });
|
||||
(axios.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
data: { data: { ...sampleProject, is_active: false } },
|
||||
});
|
||||
(axios.get as unknown as ReturnType<typeof vi.fn> | undefined)?.mockResolvedValue?.({
|
||||
data: { data: [], meta: { total: 0 } },
|
||||
});
|
||||
const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });
|
||||
const store = useProjectsStore();
|
||||
const spy = vi.spyOn(store, 'toggleActive').mockResolvedValueOnce(undefined);
|
||||
@@ -174,33 +179,30 @@ describe('ProjectDetailsDrawer', () => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('renders region chips when project has non-zero region_mask', async () => {
|
||||
const withRegions: Project = { ...sampleProject, region_mask: 6, region_mode: 'exclude' };
|
||||
it('renders region chips for project.regions = [1, 2]', async () => {
|
||||
const withRegions: Project = { ...sampleProject, regions: [1, 2] };
|
||||
const wrapper = mount(ProjectDetailsDrawer, { props: { project: withRegions } });
|
||||
await wrapper.vm.$nextTick();
|
||||
const text = wrapper.text();
|
||||
expect(text).toContain('Адыгея');
|
||||
expect(text).toContain('Башкортостан');
|
||||
expect(text).toContain('Адыгея'); // code 1
|
||||
expect(text).toContain('Алтай'); // code 2 (Республика Алтай)
|
||||
});
|
||||
|
||||
it('selecting regions encodes mask + sets mode=exclude on save', async () => {
|
||||
it('selecting regions adds to regions array (no bitmask conversion)', async () => {
|
||||
(axios.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ data: { data: sampleProject } });
|
||||
const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });
|
||||
const autocomplete = wrapper.getComponent({ name: 'VAutocomplete' });
|
||||
await autocomplete.vm.$emit('update:model-value', [3, 5]);
|
||||
await autocomplete.vm.$emit('update:model-value', [82, 83]);
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.get('[data-testid="pdd-save"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(axios.patch).toHaveBeenCalledWith(
|
||||
'/api/projects/42',
|
||||
expect.objectContaining({ region_mask: 40, region_mode: 'exclude' }),
|
||||
);
|
||||
expect(axios.patch).toHaveBeenCalledWith('/api/projects/42', expect.objectContaining({ regions: [82, 83] }));
|
||||
});
|
||||
|
||||
it('clearing all regions resets mask=0 + mode=include on save', async () => {
|
||||
it('clearing all regions sets regions=[] on save', async () => {
|
||||
(axios.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ data: { data: sampleProject } });
|
||||
const withRegions: Project = { ...sampleProject, region_mask: 6, region_mode: 'exclude' };
|
||||
const withRegions: Project = { ...sampleProject, regions: [82, 83] };
|
||||
const wrapper = mount(ProjectDetailsDrawer, { props: { project: withRegions } });
|
||||
const autocomplete = wrapper.getComponent({ name: 'VAutocomplete' });
|
||||
await autocomplete.vm.$emit('update:model-value', []);
|
||||
@@ -208,9 +210,6 @@ describe('ProjectDetailsDrawer', () => {
|
||||
await wrapper.get('[data-testid="pdd-save"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(axios.patch).toHaveBeenCalledWith(
|
||||
'/api/projects/42',
|
||||
expect.objectContaining({ region_mask: 0, region_mode: 'include' }),
|
||||
);
|
||||
expect(axios.patch).toHaveBeenCalledWith('/api/projects/42', expect.objectContaining({ regions: [] }));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,28 +15,26 @@ describe('SettingsView.vue', () => {
|
||||
expect(wrapper.find('h1').text()).toBe('Настройки');
|
||||
});
|
||||
|
||||
it('содержит ровно 8 nav-tabs', () => {
|
||||
it('содержит ровно 4 nav-tabs (placeholder-вкладки убраны, audit D6/D7)', () => {
|
||||
const wrapper = factory();
|
||||
const items = wrapper.findAll('.tabs-rail .v-list-item');
|
||||
expect(items.length).toBe(8);
|
||||
expect(items.length).toBe(4);
|
||||
});
|
||||
|
||||
it('содержит все 8 названий вкладок', () => {
|
||||
it('содержит все 4 названия рабочих вкладок', () => {
|
||||
const wrapper = factory();
|
||||
const text = wrapper.text();
|
||||
const labels = [
|
||||
'Профиль',
|
||||
'Безопасность',
|
||||
'Проекты',
|
||||
'Команда',
|
||||
'API и Webhook',
|
||||
'Интеграции',
|
||||
'Тихие часы',
|
||||
'Уведомления',
|
||||
];
|
||||
const labels = ['Профиль', 'Безопасность', 'API и Webhook', 'Уведомления'];
|
||||
labels.forEach((l) => expect(text).toContain(l));
|
||||
});
|
||||
|
||||
it('не содержит placeholder-вкладок и текста «В разработке»', () => {
|
||||
const wrapper = factory();
|
||||
const railText = wrapper.find('.tabs-rail').text();
|
||||
['Команда', 'Интеграции', 'Тихие часы'].forEach((l) => expect(railText).not.toContain(l));
|
||||
expect(wrapper.text()).not.toContain('В разработке');
|
||||
});
|
||||
|
||||
it('по умолчанию показывает вкладку «Профиль»', () => {
|
||||
const wrapper = factory();
|
||||
const text = wrapper.text();
|
||||
@@ -46,17 +44,6 @@ describe('SettingsView.vue', () => {
|
||||
expect(text).toContain('Тайм-зона');
|
||||
});
|
||||
|
||||
it('placeholder-вкладки показывают «В разработке»', async () => {
|
||||
const wrapper = factory();
|
||||
// Кликаем по «Проекты» — placeholder-вкладка.
|
||||
const items = wrapper.findAll('.tabs-rail .v-list-item');
|
||||
const projectsItem = items.find((i) => i.text().includes('Проекты'));
|
||||
expect(projectsItem).toBeDefined();
|
||||
await projectsItem!.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.text()).toContain('В разработке');
|
||||
});
|
||||
|
||||
it('переключение на «Уведомления» показывает матрицу 8×3', async () => {
|
||||
const wrapper = factory();
|
||||
const items = wrapper.findAll('.tabs-rail .v-list-item');
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Глоссарий проекта Лидерра
|
||||
# Формат: одно слово на строке. Кириллица в нижнем регистре.
|
||||
|
||||
# A4 design-tooling integration (v2.8 / v3.8 / v1.22)
|
||||
iconify
|
||||
|
||||
# Бренд и термины проекта
|
||||
лидерра
|
||||
liderra
|
||||
@@ -1348,6 +1351,11 @@ RAG
|
||||
venv
|
||||
Helicone
|
||||
Langfuse
|
||||
sickn
|
||||
antigravity
|
||||
sqlite
|
||||
воркфлоу
|
||||
эксцепшн
|
||||
|
||||
# SG #40 Security Guidance correction (2026-05-17)
|
||||
резолва
|
||||
@@ -1360,3 +1368,20 @@ PRD
|
||||
automazeio
|
||||
prds
|
||||
Vivek
|
||||
|
||||
# deptrac architecture-fitness integration (2026-05-17)
|
||||
deptrac
|
||||
qossmic
|
||||
mermaidjs
|
||||
graphviz
|
||||
|
||||
# A3 integration-tooling design spec + plan (2026-05-17)
|
||||
аудировал
|
||||
JVM
|
||||
хендлеров
|
||||
ivo
|
||||
redocly
|
||||
ivotoby
|
||||
ребейз
|
||||
ребейзнута
|
||||
ребейзом
|
||||
|
||||
+2
-1
@@ -24,7 +24,8 @@
|
||||
"*.svg",
|
||||
"**/*.sql",
|
||||
".claude/skills/mermaid/**",
|
||||
".claude/skills/ccpm/**"
|
||||
".claude/skills/ccpm/**",
|
||||
".claude/skills/data-scientist/**"
|
||||
],
|
||||
"ignoreRegExpList": [
|
||||
"Email",
|
||||
|
||||
+27
-2
@@ -1,11 +1,36 @@
|
||||
# CHANGELOG schema.sql — Лидерра
|
||||
|
||||
**Назначение:** консолидированный журнал изменений `schema.sql`. Содержит двадцать записей в обратном хронологическом порядке (v8.21 → v8.20 → v8.19 → v8.18 → v8.17 → v8.16 → v8.15 → v8.14 → v8.13 → v8.12 → v8.11 → v8.10 → v8.9 → v8.8 → v8.7 → v8.6 → v8.5 → v8.4 → v8.3 → v8.2), как принято в keep-a-changelog.
|
||||
**Назначение:** консолидированный журнал изменений `schema.sql`. Содержит двадцать одну запись в обратном хронологическом порядке (v8.22 → v8.21 → v8.20 → v8.19 → v8.18 → v8.17 → v8.16 → v8.15 → v8.14 → v8.13 → v8.12 → v8.11 → v8.10 → v8.9 → v8.8 → v8.7 → v8.6 → v8.5 → v8.4 → v8.3 → v8.2), как принято в keep-a-changelog.
|
||||
|
||||
**Файл схемы:** `schema.sql` (текущая версия — v8.21, консолидированная — разворачивает БД с нуля).
|
||||
**Файл схемы:** `schema.sql` (текущая версия — v8.22, консолидированная — разворачивает БД с нуля).
|
||||
|
||||
**История записей:**
|
||||
|
||||
## v8.22 — 2026-05-17 — Plan 6 (C9 — Subject-level regions)
|
||||
|
||||
**Изменения:**
|
||||
|
||||
- `projects` +1 колонка: `regions INT[] NOT NULL DEFAULT '{}'`
|
||||
- `projects` +1 GIN-индекс: `idx_projects_regions`
|
||||
- `projects` +1 COMMENT ON COLUMN на `regions`
|
||||
|
||||
**Не изменено (deprecated, удаление в Plan 6.5):**
|
||||
|
||||
- `projects.region_mask` (помечен inline-комментарием DEPRECATED)
|
||||
- `projects.region_mode`
|
||||
- CHECK `chk_projects_region_mask_range`
|
||||
|
||||
**Семантика:**
|
||||
|
||||
- `regions=[]` → «вся РФ» (паритет с legacy `region_mask=255 + region_mode='include'`)
|
||||
- `regions=[82,83]` → проект принимает лиды только из Москвы (82) и Санкт-Петербурга (83)
|
||||
|
||||
**Schema baseline после v8.22:** 64 базовых таблиц / 12 партиций / **119 индексов** (+1 GIN) / 40 RLS / 5 функций / 13 триггеров.
|
||||
|
||||
**Применение:** инкрементальная миграция `2026_05_17_100000_plan6_regions_subject_level.php` (`ALTER TABLE projects ADD COLUMN regions` + `CREATE INDEX ... USING GIN`, guard'ы `hasColumn` / `IF NOT EXISTS`).
|
||||
|
||||
**Связано:** docs/superpowers/specs/2026-05-14-plan-6-regions-subject-level-design.md
|
||||
|
||||
## v8.21 — 2026-05-16 — Sprint 4 (историческая миграция лидов §6)
|
||||
|
||||
- **+1 таблица** `import_unknown_statuses` (tenant-level маппинг неизвестных статусов CSV; RLS `tenant_isolation`; UNIQUE `(tenant_id, status_ru)`; partial index `idx_import_unknown_statuses_unresolved`).
|
||||
|
||||
+13
-3
@@ -1,7 +1,7 @@
|
||||
-- =============================================================================
|
||||
-- schema.sql — единая схема БД для SaaS-аналога crm.bp-gr.ru («Лидерра»)
|
||||
-- Версия: v8.21 (16.05.2026 — Sprint 4: import_unknown_statuses + import_log enrichment (+5 колонок))
|
||||
-- Метрики: 64 базовые таблицы (62 regular + 2 partitioned parents: deals + supplier_lead_costs) + 12 партиций / 118 индексов / 40 RLS-политик / 5 функций / 13 триггеров
|
||||
-- Версия: v8.22 (17.05.2026 — Plan 6 (C9): projects.regions INT[] subject-level filtering + GIN-индекс idx_projects_regions)
|
||||
-- Метрики: 64 базовые таблицы (62 regular + 2 partitioned parents: deals + supplier_lead_costs) + 12 партиций / 119 индексов / 40 RLS-политик / 5 функций / 13 триггеров
|
||||
-- Базовая версия: v8.20 (11.05.2026 — Plan 5 frontend projects UI: projects.archived_at TIMESTAMPTZ NULL для soft archive flow; tenants.limits JSONB NOT NULL DEFAULT '{}' для per-tenant project/user лимитов)
|
||||
-- Базовая версия: v8.19 (11.05.2026 — Plan 4 billing+csv+admin: tenants.delivered_in_month, lead_charges.charge_source + CHECK, supplier_leads.recovered_from_csv_at, supplier_csv_reconcile_log)
|
||||
-- Базовая версия: v8.18 (10.05.2026 — Plan 2/5 Task 1: supplier_leads SaaS-level + projects.delivered_today + 2 system_settings rows для supplier-webhook + IP allowlist defense-in-depth)
|
||||
@@ -809,13 +809,18 @@ CREATE TABLE projects (
|
||||
supplier_b3_project_id BIGINT,
|
||||
effective_limit_calculated_at TIMESTAMPTZ,
|
||||
-- РАСШИРЕНИЕ v8.2: регионы и дни (партия 10.3 секции 6, 7, 11)
|
||||
region_mask INT NOT NULL DEFAULT 255,
|
||||
region_mask INT NOT NULL DEFAULT 255, -- DEPRECATED Plan 6.5: см. regions INT[]
|
||||
-- битмаска 8 ФО РФ: бит 1=Центральный, 2=Северо-Западный, 4=Южный,
|
||||
-- 8=Северо-Кавказский, 16=Приволжский, 32=Уральский, 64=Сибирский,
|
||||
-- 128=Дальневосточный. 255 = все 8 округов.
|
||||
region_mode VARCHAR(10) NOT NULL DEFAULT 'include'
|
||||
CHECK (region_mode IN ('include','exclude')),
|
||||
-- 'include' = принимать только из выбранных, 'exclude' = принимать кроме выбранных
|
||||
-- v8.20 (Plan 6): Subject-level regions array. 89 codes из resources/js/constants/regions.ts.
|
||||
-- Пустой массив = «вся РФ» (паритет с legacy region_mask=255 + region_mode='include').
|
||||
-- region_mask/region_mode остаются для legacy reader'ов (PhonePrefixService, LeadRouter),
|
||||
-- DEPRECATED — удаляются в Plan 6.5 после переключения читателей.
|
||||
regions INT[] NOT NULL DEFAULT '{}'::INT[],
|
||||
delivery_days_mask INT NOT NULL DEFAULT 127,
|
||||
-- битмаска дней недели: бит 1=Пн, 2=Вт, 4=Ср, 8=Чт, 16=Пт, 32=Сб, 64=Вс.
|
||||
-- 127 = все 7 дней (паритет с формой создания нового проекта в оригинале).
|
||||
@@ -863,6 +868,8 @@ CREATE INDEX idx_projects_tag ON projects(tag);
|
||||
-- РАСШИРЕНИЕ v8.12: composite index для lookup по signal-полям (resolveSignalSource)
|
||||
CREATE INDEX idx_projects_tenant_signal
|
||||
ON projects(tenant_id, signal_type, signal_identifier);
|
||||
-- v8.20 (Plan 6): GIN-индекс для outbound regions queries.
|
||||
CREATE INDEX idx_projects_regions ON projects USING GIN (regions);
|
||||
|
||||
COMMENT ON COLUMN projects.daily_limit_target IS
|
||||
'Целевой дневной лимит лидов, заданный клиентом. Фактический лимит на '
|
||||
@@ -874,6 +881,9 @@ COMMENT ON COLUMN projects.effective_daily_limit_today IS
|
||||
'MIN(daily_limit_target, FLOOR(balance / lead_cost)). Пересчитывается cron '
|
||||
'limits:recalc в 00:00 МСК и при изменении баланса. NULL = не считалось.';
|
||||
|
||||
COMMENT ON COLUMN projects.regions IS
|
||||
'Subject-level region filter (1..89 коды субъектов РФ). Пустой массив = вся РФ. Plan 6 (v8.22).';
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- supplier_projects — SaaS-level агрегатные проекты у поставщиков (v8.13, Plan 1/5 Task 2)
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
# Plugin Stack Rules — Superpowers + Frontend Design (v3.6)
|
||||
# Plugin Stack Rules — Superpowers + Frontend Design (v3.10)
|
||||
|
||||
**Дата:** 17.05.2026
|
||||
**Назначение:** свод правил совместного использования плагинов Claude Code в проекте Лидерра — paired-stack ядро `obra/superpowers` (14 skills) + `anthropics/frontend-design`, плюс расширенный пул UI-инструментов `ui-ux-pro-max` (skill, marketplace `nextlevelbuilder/ui-ux-pro-max-skill`) и `21st.dev Magic MCP` (MCP-сервер `magic`), плюс инфраструктурный `claude-md-management` (skills, marketplace `anthropics/claude-plugins-official`), плюс **debug-runtime MCP** `@sentry/mcp-server` + `@modelcontextprotocol/server-redis` (v2.1+, R10.1 Блок 3).
|
||||
|
||||
**v3.10** — A11 ml-ai-tooling: R10.1 Блок 3 +1 строка **Jupyter MCP** (DEFERRED — требует Python ML-окружения; ml-ai-tooling, off-phase, раздел A11 карты) + Блок 1 note (v3.10) — **promptfoo** (npm devDependency `promptfoo`, CLI-eval LLM-промптов) + **Data Scientist skill** (вендоренный сторонний скил `.claude/skills/data-scientist/`). Десятая off-phase подкатегория ml-ai-tooling. Не UI → вне R6/R14. Содержательных изменений R0–R14: 0. Связано: Tooling v2.10, Pravila v1.24, CLAUDE.md v2.10; план `docs/superpowers/plans/2026-05-17-a11-ml-ai-tooling-integration.md`.
|
||||
|
||||
**v3.9** — A3 integration-tooling: R10.1 Блок 3 +1 строка **openapi-mcp-server** (категория integration-tooling, off-phase, раздел A3 карты, stdio MCP, server `openapi` в `.mcp.json`, Tooling §4.22 #47). Не UI → вне R6/R14. Содержательных изменений R0–R14: 0. Связано: Tooling v2.9, Pravila v1.23, CLAUDE.md v2.9; план `docs/superpowers/plans/2026-05-17-a3-integration-tooling-integration.md`.
|
||||
|
||||
**v3.7** — A6-расширение deptrac: R10.1 Блок 1 +note «Блок 1 — note (v3.7)» — **deptrac** (`deptrac/deptrac` v4.6.1, Composer dev-dependency, **не** marketplace-плагин и **не** в `enabledPlugins` — регистрируется нотой, как mermaid-skill/CCPM). Категория **architecture-tooling** (Tooling #43, раздел A6 карты) — 4-й инструмент подкатегории; не UI → вне R6.0/R6.1/R14. deptrac врезан как lefthook pre-commit job 10. Содержательных изменений R0–R14: 0. Связано: Tooling v2.7, Pravila v1.21, CLAUDE.md v2.7; план `docs/superpowers/plans/2026-05-17-deptrac-architecture-fitness-integration.md`.
|
||||
|
||||
**v3.6** — C9 project-management: R10.1 Блок 1 (`enabledPlugins`) +2 строки — **CCPM** (`automazeio/ccpm`, вендорен в `.claude/skills/ccpm/`) + **product-management** (`anthropics/knowledge-work-plugins`, Anthropic Verified). Блок 1 +note про **CCPM** (вендоренный скил, аналог mermaid-skill). Новая категория **project-management** (Tooling #41-42, раздел C9 карты) — не UI → вне R6.0/R6.1/R14, как architecture-tooling/audit-security. Содержательных изменений R0–R14: 0. Связано: Tooling v2.6, Pravila v1.20, CLAUDE.md v2.6; план `docs/superpowers/plans/2026-05-17-c9-project-management-tooling-integration.md`.
|
||||
|
||||
**v3.5** — фактическая правка R10.1 Блок 1 строки **security-guidance**: это **блокирующий** PreToolUse-хук (`sys.exit(2)`, одноразовый speed-bump per «файл+правило» за сессию, retry проходит), не warn-only. Содержательных изменений R0–R14: 0. Связано: Tooling v2.5, Pravila v1.19, CLAUDE.md v2.5; план `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`.
|
||||
@@ -406,11 +412,16 @@ Stack — **головной**. Все плагины вне stack'а — **ин
|
||||
| **security-guidance** *(1 PreToolUse-хук, блокирующий)* | `anthropics/claude-plugins-official` | inline-предупреждения уязвимостей при правке кода — **блокирующий** хук (`sys.exit 2`, одноразовый speed-bump per «файл+правило» за сессию, retry проходит), 8 контентных правил + 1 path-правило. Категория: **audit-security** (Tooling #40) | автоматически — PreToolUse-хук на Write/Edit/MultiEdit. Не решатель, не UI → вне R6.0/R6.1/R14 |
|
||||
| **CCPM** *(vendored standalone skill, `/pm` flow, 14 bash-скриптов)* | `automazeio/ccpm` (вендорен в `.claude/skills/ccpm/`) | PRD→эпик→GitHub-issue→код с полной трассируемостью. GitHub-issue-backed модель (ADR-004). PRD/epic store в `.claude/prds/`/`.claude/epics/`. Категория: **project-management** (Tooling #41, вне UI-пула). Bus-factor mitigation — вендорен (community-проект). 0 хуков | при авторинге PRD/epic и создании GitHub-issue из CCPM flow. Не UI → вне R6.0/R6.1/R14 |
|
||||
| **product-management** *(6 команд `/write-spec`, `/roadmap-update` и др.)* | `anthropics/knowledge-work-plugins` (plugin `product-management@knowledge-work-plugins`, Anthropic Verified) | product-strategy церемонии (problem→spec, roadmap, stakeholder updates, research synthesis, competitive analysis, metrics review). Категория: **project-management** (Tooling #42). 0 хуков | при product-strategy work: написание спеки, обновление роадмапа, анализ конкурентов. Не UI → вне R6.0/R6.1/R14 |
|
||||
| **Design plugin** *(Design Critique / Accessibility Audit / UX Writing / Research Synthesis)* | `anthropics/knowledge-work-plugins` (Anthropic Verified) | дизайн-критика и UX — ревью макетов, дизайн-уровневый a11y-аудит, UX-копирайт, research synthesis. Категория: **design-tooling** (Tooling #46, вне UI-пула) | при дизайн-критике макета, UX-анализе, написании микрокопирайта — pre-code (ADR-006). Не подменяет FD #30 (генерация) и `requesting-code-review`. Не UI → вне R6.0/R6.1/R14 |
|
||||
|
||||
**Блок 1 — note (v3.3):** **mermaid-skill** (Tooling #37, генератор C4/architecture-диаграмм) — вендоренный сторонний скил в `.claude/skills/mermaid/` (`WH-2099/mermaid-skill`, MIT), **не** через marketplace и **не** в `enabledPlugins`. Пассивная утилита (генерация Mermaid-исходника), не решатель — формально вне типологии трёх блоков; регистрируется здесь для полноты. Категория **architecture-tooling**, вне R6/R14.
|
||||
|
||||
**Блок 1 — note (v3.6):** **CCPM** (Tooling #41) — аналогично mermaid-skill: вендоренный сторонний скил в `.claude/skills/ccpm/` (`automazeio/ccpm`, MIT), **не** через marketplace и **не** в `enabledPlugins`. Активируется через `/pm` в Claude Code. Категория **project-management**, вне R6/R14.
|
||||
|
||||
**Блок 1 — note (v3.7):** **deptrac** (Tooling #43, архитектурный fitness-гейт) — Composer dev-dependency (`deptrac/deptrac` v4.6.1, BSD-3), **не** marketplace-плагин и **не** в `enabledPlugins`; врезан как lefthook pre-commit job 10 (`deptrac analyse` на staged `app/**/*.php`). CLI-инструмент статического анализа направления зависимостей между слоями — не решатель; формально вне типологии трёх блоков, регистрируется здесь для полноты. Категория **architecture-tooling** (как adr-kit/architecture-patterns), вне R6.0/R6.1/R14.
|
||||
|
||||
**Блок 1 — note (v3.10):** **promptfoo** (Tooling #48, ml-ai-tooling) — npm devDependency (`promptfoo`, MIT) в корневом `package.json`, **не** marketplace-плагин и **не** в `enabledPlugins`; CLI-инструмент eval LLM-промптов, запуск `npx promptfoo` вручную/CI (платные LLM-вызовы — никогда в хук, ML1). **Data Scientist skill** (Tooling #49, ml-ai-tooling) — аналогично mermaid-skill/CCPM: вендоренный сторонний скил в `.claude/skills/data-scientist/` (`sickn33/antigravity-awesome-skills`, код MIT / контент CC BY 4.0), **не** через marketplace. Оба формально вне типологии трёх блоков, регистрируются здесь для полноты. Категория **ml-ai-tooling** (раздел A11 карты), вне R6.0/R6.1/R14.
|
||||
|
||||
**Отмена:** через удаление из `enabledPlugins` в `~/.claude/settings.json` или через live-override `/имя-плагина` (R0.4.B) на одно действие.
|
||||
|
||||
#### Блок 2: Built-in skills Claude Code (всегда доступны через `Skill` tool по `/имя`)
|
||||
@@ -440,6 +451,10 @@ Stack — **головной**. Все плагины вне stack'а — **ин
|
||||
| **github** | `.mcp.json` (HTTP `api.githubcopilot.com/mcp`, требует `GITHUB_TOKEN`) | официальный hosted GitHub MCP — issues, PRs, search-code, list-commits и т.д. | при работе с GitHub-объектами (PR, issues, branches) |
|
||||
| **sentry** *(`@sentry/mcp-server@0.33.0+`, official; tools `mcp__sentry__*`)* | `.mcp.json` (env `SENTRY_URL` + `SENTRY_AUTH_TOKEN` через PowerShell User scope) | **debug-runtime MCP** — production error investigation в self-hosted Sentry (Yandex Cloud per CLAUDE.md §2). Категория **debug-runtime** (v2.1+) — отдельная от UI-пула (UPM/21st) и инфраструктурного (claude-md-management). Tooling #34. Pending Sentry instance deployment (Б-1) | при investigation runtime error / post-incident debug. READ-ONLY scope (`org:read`/`project:read`/`event:read`). Не trigger'ит R6.0/R6.1 фильтры и не входит в R14 pipeline UI-генераторов |
|
||||
| **redis** *(`@modelcontextprotocol/server-redis@2025.4.25`, deprecated Anthropic source)* | `.mcp.json` (`redis://localhost:6379` к Memurai Windows service) | **debug-runtime MCP** — Redis/Memurai inspection (очереди, кэш, Pest --parallel race conditions per quirk 72/77). Категория **debug-runtime** (v2.1+). Tooling #35. Memurai PONG verified Task 4. Migration plan на community alternative (e.g., `@easy-mcps/redis-mcp-server@1.0.8`) post-MVP | при debug Redis runtime. **READ-ONLY usage обязателен** — никаких DEL/FLUSHDB/SET/LPUSH из Claude. Manual mutations — через `memurai-cli` напрямую заказчиком. Cosmetic deprecation warning в stderr |
|
||||
| **Universal Icons MCP** *(`universal-icons` сервер, tools `search_icons`/`get_icon`/`health_check`)* | `.mcp.json` (`npx -y mcp-universal-icons`, MIT) | поиск/вставка SVG-иконок — 10 коллекций включая Lucide (брендовый icon-set). Категория: **design-tooling** (Tooling #45) | при поиске иконки для Vue-компонента. НЕ запрашивать jsx/Tailwind-формат (R6.0). Материал, не решатель (R10.2). Вне R14 pipeline |
|
||||
| **Figma MCP** *(remote `https://mcp.figma.com/mcp`)* — **DEFERRED** | `.mcp.json` (HTTP-транспорт, OAuth) — не установлен, precondition: Figma-аккаунт | извлечение дизайн-токенов/variables из Figma-источника (`get_variable_defs`). **Extract-only** (ADR-006) — code-gen не используется. Категория: **design-tooling** (Tooling #44) | DEFERRED (FM2 — у проекта нет Figma-файла). При появлении Figma-аккаунта. Extract-only — FD #30 остаётся UI-решателем. Вне R6.0/R6.1/R14 |
|
||||
| **openapi-mcp-server** *(`openapi` сервер, tools `mcp__openapi__*`)* | `.mcp.json` (stdio MCP, env `OPENAPI_SPEC_URL` или локальный файл) | **integration-tooling MCP** — OpenAPI/Swagger-спецификации интеграций (inspect, introspect внешних API). Категория: **integration-tooling** (Tooling §4.22 #47). Раздел A3 карты «Программирование — интеграции (API, вебхуки)». Off-phase | при работе с внешними API-интеграциями (introspection спецификаций). **READ-ONLY introspection** — не мутировать внешние API из Claude. Не trigger'ит R6.0/R6.1 фильтры и не входит в R14 pipeline UI-генераторов. Вне R6/R14 |
|
||||
| **Jupyter MCP** *(`jupyter` сервер)* — **DEFERRED** | `.mcp.json` (stdio MCP) — не установлен, precondition: Python ML-окружение | **ml-ai-tooling MCP** — исполняемые ноутбуки (классический ML: обучение моделей). Категория: **ml-ai-tooling** (Tooling §4.25 #50). Раздел A11 карты «ML / AI-разработка». Off-phase | DEFERRED — на native-Windows машине нет Python ML-рантайма и нет модели для обучения. Зарегистрирован как pending-слот (как Figma MCP); устанавливается отдельной severable-задачей при появлении конкретной модели. Вне R6/R14 |
|
||||
|
||||
**Отмена:** через удаление из `~/.claude.json` или `.mcp.json`. Live-override через `/команду` для MCP не предусмотрен — MCP-серверы не имеют slash-интерфейса.
|
||||
|
||||
@@ -765,6 +780,10 @@ Pipeline активируется при одновременном выполн
|
||||
|
||||
## История версий
|
||||
|
||||
- **v3.8 (2026-05-17)** — A4 design-tooling: R10.1 Блок 1 +Design plugin (`anthropics/claude-plugins-official`, Anthropic Verified) — дизайн-критика и UX, новая 8-я off-phase подкатегория design-tooling; Блок 3 +Universal Icons MCP (`npx -y mcp-universal-icons`, MIT) + Figma MCP (remote `https://mcp.figma.com/mcp`, DEFERRED). Не UI → вне R6.0/R6.1/R14. Содержательных изменений R0–R14: 0. Связано: Tooling v2.8, Pravila v1.22, CLAUDE.md v2.8. План `docs/superpowers/plans/2026-05-17-a4-design-tooling-integration.md`.
|
||||
|
||||
- **v3.7 (2026-05-17)** — A6-расширение deptrac: R10.1 Блок 1 +note «Блок 1 — note (v3.7)» — **deptrac** (`deptrac/deptrac` v4.6.1, BSD-3, Composer dev-dependency — **не** marketplace-плагин и **не** в `enabledPlugins`, регистрируется нотой как mermaid-skill/CCPM; врезан lefthook pre-commit job 10). Категория **architecture-tooling** (Tooling #43, раздел A6 карты) — 4-й инструмент подкатегории, не UI → вне R6.0/R6.1/R14. Содержательных изменений R0–R14: 0. Связано: Tooling v2.7, Pravila v1.21, CLAUDE.md v2.7. План `docs/superpowers/plans/2026-05-17-deptrac-architecture-fitness-integration.md`.
|
||||
|
||||
- **v3.6 (2026-05-17)** — C9 project-management: R10.1 Блок 1 (`enabledPlugins`) +2 строки (**CCPM** `automazeio/ccpm` вендорен в `.claude/skills/ccpm/`, **product-management** `anthropics/knowledge-work-plugins` Anthropic Verified) + Блок 1 note про CCPM (vendored скил, аналог mermaid-skill). Новая категория **project-management** (Tooling #41-42, раздел C9 карты «Управление проектами») — 7-я off-phase подкатегория, не UI → вне R6.0/R6.1/R14. Содержательных изменений R0–R14: 0. Связано: Tooling v2.6, Pravila v1.20, CLAUDE.md v2.6. План `docs/superpowers/plans/2026-05-17-c9-project-management-tooling-integration.md`.
|
||||
|
||||
- **v3.5 (2026-05-17)** — фактическая правка R10.1 Блок 1 (security-guidance): хук **блокирующий** (`sys.exit(2)`, одноразовый speed-bump per «файл+правило» за сессию), не warn-only. Содержательных изменений R0–R14: 0. Связано: Tooling v2.5, Pravila v1.19, CLAUDE.md v2.5. План `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`.
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
# Правила работы Claude в проекте «Лидерра»
|
||||
|
||||
**Версия:** v1.20 (17.05.2026)
|
||||
**Версия:** v1.24 (17.05.2026)
|
||||
**Дата:** 17.05.2026
|
||||
**Назначение:** настройки проекта (Project instructions) — Claude читает этот файл в каждом чате и следует правилам ниже.
|
||||
**Статус документа:** ✅ утверждён. Содержимое скопировано в поле "Project instructions" Claude.ai. Файл хранится в архиве как служебный документ.
|
||||
|
||||
**Что изменилось в v1.24 относительно v1.23:** §13.2 +абзац «Off-phase ml-ai-tooling» — формализованы инструменты раздела A11 карты «ML / AI-разработка» (#48 promptfoo, #49 Data Scientist skill, #50 Jupyter MCP DEFERRED) как десятая off-phase подкатегория; promptfoo делает платные LLM-вызовы — только вручную/CI, никогда в хук (ML1). Границы — ADR-007. Связано: Tooling v2.10 / PSR_v1 v3.10 / CLAUDE.md v2.10. План `docs/superpowers/plans/2026-05-17-a11-ml-ai-tooling-integration.md`.
|
||||
|
||||
**Что изменилось в v1.23 относительно v1.22:** §13.2 +абзац «Off-phase integration-tooling» — формализованы инструменты раздела A3 карты «Программирование — интеграции (API, вебхуки)» (#47 openapi-mcp-server, api-docs agent) как девятая off-phase подкатегория; READ-ONLY introspection. Связано: Tooling v2.9 / PSR_v1 v3.9 / CLAUDE.md v2.9. План `docs/superpowers/plans/2026-05-17-a3-integration-tooling-integration.md`.
|
||||
|
||||
**Что изменилось в v1.22 относительно v1.21:** §13.2 +абзац «Off-phase design-tooling» — формализованы 3 инструмента раздела A4 карты «Дизайн (UI/UX, графика, бренд)» (#44 Figma MCP DEFERRED, #45 Universal Icons MCP, #46 Design plugin) как восьмая off-phase подкатегория; §13.2 PSR_v1 cross-ref синхронизирован → v3.8+. Связано: Tooling v2.8 / PSR_v1 v3.8 / CLAUDE.md v2.8. План `docs/superpowers/plans/2026-05-17-a4-design-tooling-integration.md`.
|
||||
|
||||
**Что изменилось в v1.21 относительно v1.20:** §13.2 абзац «Off-phase architecture-tooling» расширен — формализован 4-й инструмент раздела A6 карты «Архитектура систем» (#43 deptrac, Composer dev-dependency `deptrac/deptrac` v4.6.1; архитектурный fitness-гейт направления зависимостей / границ слоёв, врезан в lefthook pre-commit job 10). Категория architecture-tooling без изменений (та же пятая off-phase). Связано: Tooling v2.7 / PSR_v1 v3.7 / CLAUDE.md v2.7; план `docs/superpowers/plans/2026-05-17-deptrac-architecture-fitness-integration.md`.
|
||||
|
||||
**Что изменилось в v1.20 относительно v1.19:** §13.2 +абзац «Off-phase project-management» — формализованы 2 инструмента раздела C9 карты «Управление проектами» (#41 CCPM, #42 product-management) как седьмая off-phase категория; §13.2 PSR_v1 cross-ref v3.5+ → v3.6+. Связано: Tooling v2.6 / PSR_v1 v3.6 / CLAUDE.md v2.6; план `docs/superpowers/plans/2026-05-17-c9-project-management-tooling-integration.md`.
|
||||
|
||||
**Что изменилось в v1.19 относительно v1.18:** §13.2 абзац «Off-phase audit-security» — фактическая правка характеристики #40 Security Guidance: это **блокирующий** PreToolUse-хук (`sys.exit 2`, одноразовый speed-bump per «файл+правило» за сессию), не warn-only. §13.2 PSR_v1 cross-ref v3.4+ → v3.5+. Связано: Tooling v2.5 / PSR_v1 v3.5 / CLAUDE.md v2.5; план `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`.
|
||||
@@ -567,6 +575,10 @@ P0 = блокер старта спринта или регуляторного
|
||||
| **v1.18** | **17.05.2026** | D3 audit-security: §13.2 +абзац «Off-phase audit-security» — формализованы 2 инструмента раздела D3 карты «Аудит и управление рисками» (#39 Trail of Bits Skills, #40 Security Guidance) как шестая off-phase категория; §13.2 PSR_v1 cross-ref v3.3+ → v3.4+. Связано: Tooling v2.4 / PSR_v1 v3.4 / CLAUDE.md v2.4. План `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`. |
|
||||
| **v1.19** | **17.05.2026** | Фактическая правка §13.2 абзаца «Off-phase audit-security»: #40 Security Guidance — **блокирующий** PreToolUse-хук (`sys.exit 2`, одноразовый speed-bump per «файл+правило» за сессию), не warn-only; §13.2 PSR_v1 cross-ref v3.4+ → v3.5+. Связано: Tooling v2.5 / PSR_v1 v3.5 / CLAUDE.md v2.5. План `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`. |
|
||||
| **v1.20** | **17.05.2026** | C9 project-management: §13.2 +абзац «Off-phase project-management» — формализованы 2 инструмента раздела C9 карты «Управление проектами» (#41 CCPM, #42 product-management) как седьмая off-phase категория, отдельная от UI-пула / infrastructure / debug-runtime / orchestration / architecture-tooling / audit-security; не UI → вне R6.0/R6.1/R14. §13.2 PSR_v1 cross-ref v3.5+ → v3.6+. Связано: Tooling v2.6 (§4.16-4.17 + §0 счётчик 40→42), PSR_v1 v3.6 (R10.1 Блок 1 +2 строки + note), CLAUDE.md v2.6 (§3.3 +#41-42). Через manual Edit (Pravila/PSR_v1/Tooling) + `/claude-md-management:claude-md-improver` (CLAUDE.md per §5 п.10). План `docs/superpowers/plans/2026-05-17-c9-project-management-tooling-integration.md`. Архитектурных изменений в §§1–12 + §§13.1, 13.3–14: 0. |
|
||||
| **v1.21** | **17.05.2026** | A6-расширение deptrac: §13.2 абзац «Off-phase architecture-tooling» расширен — формализован 4-й инструмент раздела A6 (#43 deptrac, Composer dev-dependency `deptrac/deptrac` v4.6.1 BSD-3; архитектурный fitness-гейт направления зависимостей / границ слоёв, врезан в lefthook pre-commit job 10, конфиг `app/deptrac.yaml` 13 слоёв, первый прогон 0 нарушений → baseline не нужен, red-green доказан). Категория architecture-tooling без изменений. Связано: Tooling v2.6→v2.7 (§4.18 + §0 счётчик 42→43), PSR_v1 v3.6→v3.7 (R10.1 Блок 1 note), CLAUDE.md v2.6→v2.7 (§3.3 +#43). Через manual Edit (Pravila/PSR_v1/Tooling) + `/claude-md-management:claude-md-improver` (CLAUDE.md per §5 п.10). План `docs/superpowers/plans/2026-05-17-deptrac-architecture-fitness-integration.md`. Архитектурных изменений в §§1–12 + §§13.1, 13.3–14: 0. |
|
||||
| **v1.22** | **17.05.2026** | A4 design-tooling: §13.2 +абзац «Off-phase design-tooling» — формализованы 3 инструмента раздела A4 карты «Дизайн (UI/UX, графика, бренд)» (#44 Figma MCP / #45 Universal Icons MCP / #46 Design plugin) как восьмая off-phase подкатегория, отдельная от UI-пула / infrastructure / debug-runtime / orchestration / architecture-tooling / audit-security / project-management; не UI → вне R6.0/R6.1/R14. §13.2 PSR_v1 cross-ref v3.3+ → v3.8+ (текст застрял на v3.3+ — changelog v1.18-v1.20 заявлял bump'ы, но §13.2 не обновлялся; теперь синхронизирован). Связано: Tooling v2.8 / PSR_v1 v3.8 / CLAUDE.md v2.8. План `docs/superpowers/plans/2026-05-17-a4-design-tooling-integration.md`. |
|
||||
| **v1.23** | **17.05.2026** | A3 integration-tooling: §13.2 +абзац «Off-phase integration-tooling» — формализованы инструменты раздела A3 карты «Программирование — интеграции (API, вебхуки)» (#47 `openapi-mcp-server`, Tooling §4.22; `api-docs` agent, claude-flow, без Tooling-номера) как девятая off-phase подкатегория, отдельная от всех предыдущих; не UI → вне R6.0/R6.1/R14. READ-ONLY introspection. Регулируются PSR_v1 R10.1 Блок 3. Связано: Tooling v2.9 / PSR_v1 v3.9 / CLAUDE.md v2.9. План `docs/superpowers/plans/2026-05-17-a3-integration-tooling-integration.md`. Архитектурных изменений в §§1–12 + §§13.1, 13.3–14: 0. |
|
||||
| **v1.24** | **17.05.2026** | A11 ml-ai-tooling: §13.2 +абзац «Off-phase ml-ai-tooling» — формализованы инструменты раздела A11 карты «ML / AI-разработка» (#48 promptfoo — npm devDependency, CLI-eval LLM-промптов; #49 Data Scientist skill — вендоренный сторонний скил; #50 Jupyter MCP — DEFERRED, требует Python ML-окружения) как десятая off-phase подкатегория, отдельная от всех предыдущих; не UI → вне R6.0/R6.1/R14. promptfoo делает платные LLM-вызовы — только вручную/CI, никогда в хук (ML1). Границы — ADR-007. Связано: Tooling v2.10 / PSR_v1 v3.10 / CLAUDE.md v2.10. Через manual Edit всех 4 нормативных файлов (claude-md-management неприменим — исполнение в worktree, §5 п.10 worktree-constraint эксцепшн). План `docs/superpowers/plans/2026-05-17-a11-ml-ai-tooling-integration.md`. Архитектурных изменений в §§1–12 + §§13.1, 13.3–14: 0. |
|
||||
|
||||
---
|
||||
|
||||
@@ -682,7 +694,7 @@ P0 = блокер старта спринта или регуляторного
|
||||
|
||||
### 13.2. Парность со Superpowers + расширенный пул UI-инструментов (v1.8)
|
||||
|
||||
Frontend Design и `obra/superpowers` (v5.1.0, 14 skills) — **парный stack одного приоритетного уровня**. Оба плагина подключены к gate stack'а одновременно, между ними нет иерархии. Координация — через [docs/Plugin_stack_rules_v1.md](Plugin_stack_rules_v1.md) **v3.3+ (R0 — top-of-stack gate; ruflo big-bang 15.05.2026 + реколлаж 16.05.2026; полный детализированный реестр правил в PSR_v1)**.
|
||||
Frontend Design и `obra/superpowers` (v5.1.0, 14 skills) — **парный stack одного приоритетного уровня**. Оба плагина подключены к gate stack'а одновременно, между ними нет иерархии. Координация — через [docs/Plugin_stack_rules_v1.md](Plugin_stack_rules_v1.md) **v3.8+ (R0 — top-of-stack gate; ruflo big-bang 15.05.2026 + реколлаж 16.05.2026; полный детализированный реестр правил в PSR_v1)**.
|
||||
|
||||
**Расширенный пул UI-инструментов (v1.8)** добавляет к paired-stack ядру два внешних плагина в роли **инструментов** (R10.1 PSR_v1, не решателей):
|
||||
|
||||
@@ -701,12 +713,18 @@ Frontend Design и `obra/superpowers` (v5.1.0, 14 skills) — **парный sta
|
||||
|
||||
**Off-phase MCP debug-runtime (отдельная категория, введена v1.13 Pravila, 13.05.2026 day +1):** `@sentry/mcp-server@0.33.0+` (Tooling #34, server `sentry` в `.mcp.json`) — отладка production errors в self-hosted Sentry (Yandex Cloud per CLAUDE.md §2; pending Б-1 ООО registration); `@modelcontextprotocol/server-redis@2025.4.25` (Tooling #35, server `redis` в `.mcp.json`; deprecated Anthropic source; Memurai PONG verified Task 4) — отладка Redis/Memurai runtime (очереди, кэш, Pest --parallel races per quirk 72/77). **Категория отдельная** от UI-пула (§13.2 paired-stack + UPM + 21st) и от infrastructure (claude-md-management §13.2 paragraph выше) — **не trigger'ит R6.0/R6.1 stack-фильтры** (READ-ONLY, не модифицируют code/UI/CLAUDE.md) и **не входит в R14 pipeline** UI-генераторов. Регулируется PSR_v1 R10.1 Блок 3 (`.mcp.json`-серверы) как debug-runtime off-phase tool. READ-ONLY usage обязателен — никаких mutation операций (DEL/FLUSHDB/SET/LPUSH для Redis; write actions для Sentry). Установлены retrospective на feat/claude-automation `6f7e7d7` (sentry) + `bd4ec48` (redis), merged через PR #3 (`cc5f63b`). PSR_v1 cross-ref: **v3.6+**, R10.1 Блок 3.
|
||||
|
||||
**Off-phase architecture-tooling (отдельная категория, v1.17, 17.05.2026):** три инструмента раздела A6 карты «Архитектура систем» — `adr-kit` (Tooling #36, marketplace `rvdbreemen/adr-kit`; ADR-решения в `docs/adr/`, `adr-judge` врезан в lefthook pre-commit job 9 декларативно, без `--llm`), `mermaid-skill` (Tooling #37, вендоренный сторонний скил `.claude/skills/mermaid/`; C4/architecture-диаграммы), `architecture-patterns` (Tooling #38, marketplace `secondsky/claude-skills`; knowledge-only справочник паттернов). **Категория отдельная** от UI-пула (UPM/21st), infrastructure (claude-md-management) и debug-runtime (Sentry/Redis) — не UI, **не trigger'ит R6.0/R6.1 stack-фильтры и не входит в R14 pipeline**. Регулируется PSR_v1 R10.1 Блок 1 (adr-kit, architecture-patterns) + Блок 1 note (mermaid-skill — вендоренный скил вне типологии трёх блоков). Установлены 17.05.2026 на ветке `feat/a6-architecture-tooling`; план `docs/superpowers/plans/2026-05-17-a6-architecture-tooling-integration.md`.
|
||||
**Off-phase architecture-tooling (отдельная категория, v1.17, 17.05.2026; +deptrac v1.21):** четыре инструмента раздела A6 карты «Архитектура систем» — `adr-kit` (Tooling #36, marketplace `rvdbreemen/adr-kit`; ADR-решения в `docs/adr/`, `adr-judge` врезан в lefthook pre-commit job 9 декларативно, без `--llm`), `mermaid-skill` (Tooling #37, вендоренный сторонний скил `.claude/skills/mermaid/`; C4/architecture-диаграммы), `architecture-patterns` (Tooling #38, marketplace `secondsky/claude-skills`; knowledge-only справочник паттернов), `deptrac` (Tooling #43, Composer dev-dependency `deptrac/deptrac` v4.6.1 BSD-3; архитектурный fitness-гейт направления зависимостей / границ слоёв — врезан в lefthook pre-commit job 10, конфиг `app/deptrac.yaml` 13 слоёв, чистый PHP без вызовов LLM). **Категория отдельная** от UI-пула (UPM/21st), infrastructure (claude-md-management) и debug-runtime (Sentry/Redis) — не UI, **не trigger'ит R6.0/R6.1 stack-фильтры и не входит в R14 pipeline**. Регулируется PSR_v1 R10.1 Блок 1 (adr-kit, architecture-patterns) + Блок 1 notes (mermaid-skill — вендоренный скил, deptrac — composer dev-dep — оба вне типологии трёх блоков). Установлены 17.05.2026 (adr-kit/mermaid/architecture-patterns — ветка `feat/a6-architecture-tooling`, план `docs/superpowers/plans/2026-05-17-a6-architecture-tooling-integration.md`; deptrac — план `docs/superpowers/plans/2026-05-17-deptrac-architecture-fitness-integration.md`).
|
||||
|
||||
**Off-phase audit-security (отдельная категория, v1.18, 17.05.2026):** инструменты раздела D3 карты «Аудит и управление рисками» — `Trail of Bits Skills` (Tooling #39, marketplace `trailofbits/skills`; курированный субсет 8 audit-плагинов — security-аудит diff, supply-chain риск зависимостей; CC-BY-SA-4.0, marketplace-плагин не вендорен), `Security Guidance` (Tooling #40, marketplace `anthropics/claude-plugins-official`; один **блокирующий** PreToolUse-хук — inline-предупреждения уязвимостей, `sys.exit 2`, одноразовый speed-bump per «файл+правило» за сессию). Дополнительно `/security-review` (Anthropic built-in, customized в `.claude/commands/security-review.md` с проектным FP-фильтром RLS/ПДн/economy-хуки). **Категория отдельная** от UI-пула, infrastructure, debug-runtime, orchestration и architecture-tooling — не UI, **не trigger'ит R6.0/R6.1 stack-фильтры и не входит в R14 pipeline**. Регулируется PSR_v1 R10.1 Блок 1. Установлены 17.05.2026 на ветке `feat/d3-audit-risk-tooling`; план `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`.
|
||||
|
||||
**Off-phase project-management (отдельная категория, v1.20, 17.05.2026):** инструменты раздела C9 карты «Управление проектами» — `CCPM` (Tooling #41, вендоренный standalone-скил в `.claude/skills/ccpm/`, `automazeio/ccpm` MIT; PRD→эпик→GitHub-issue→код с трассируемостью через `/pm` flow; GitHub-issue-backed модель ADR-004; bus-factor — community-проект, mitigation — вендоринг), `product-management` (Tooling #42, marketplace `anthropics/knowledge-work-plugins`, Anthropic Verified; product-strategy церемонии: `/write-spec`, `/roadmap-update`, `/stakeholder-update`, `/synthesize-research`, `/competitive-brief`, `/metrics-review`). GitHub MCP (Tooling #3) reuse с `projects` toolset для GitHub Projects v2 (не новый слот). **Категория отдельная** от UI-пула, infrastructure, debug-runtime, orchestration, architecture-tooling и audit-security — не UI, **не trigger'ит R6.0/R6.1 stack-фильтры и не входит в R14 pipeline**. Регулируется PSR_v1 R10.1 Блок 1. Установлены 17.05.2026 на ветке `feat/c9-project-management-tooling`; план `docs/superpowers/plans/2026-05-17-c9-project-management-tooling-integration.md`.
|
||||
|
||||
**Off-phase design-tooling (A4):** #44 Figma MCP (extract-only, DEFERRED — у проекта нет Figma-аккаунта), #45 Universal Icons MCP, #46 Design plugin — раздел A4 карты «Дизайн (UI/UX, графика, бренд)». Восьмая off-phase подкатегория. Не UI-решатели → вне расширенного UI-пула, вне R6.0/R6.1/R14 PSR_v1. Границы — ADR-006 (Figma extract-only; Design plugin a11y дизайн-уровня — Pa11y остаётся техническим SoT; Design Critique pre-code). Регулируются PSR_v1 R10.1 (Блок 1 — Design plugin; Блок 3 — Figma MCP / Universal Icons MCP).
|
||||
|
||||
**Off-phase integration-tooling (A3):** Инструменты раздела A3 карты «Программирование — интеграции (API, вебхуки)» — #47 `openapi-mcp-server` (Tooling §4.22; введён A3-интеграцией 17.05.2026) и `api-docs` agent (claude-flow, узел карты A3 без отдельного Tooling-номера). Off-phase, не UI → вне R6/R14 PSR_v1. READ-ONLY introspection. Регулируются PSR_v1 R10.1 Блок 3.
|
||||
|
||||
**Off-phase ml-ai-tooling (A11, v1.24, 17.05.2026):** Инструменты раздела A11 карты «ML / AI-разработка» — #48 `promptfoo` (Tooling §4.23; npm devDependency, CLI-eval LLM-промптов, MIT), #49 `Data Scientist skill` (Tooling §4.24; вендоренный сторонний скил в `.claude/skills/data-scientist/`, классический ML-воркфлоу; код MIT / контент CC BY 4.0), #50 `Jupyter MCP` (Tooling §4.25; **DEFERRED** — требует Python ML-окружения, на native-Windows машине не ставится; зарегистрирован как pending-слот, как Figma MCP #44). Плюс reuse-слой — claude-api skill (PSR_v1 R10.1 Блок 2), context7 MCP, Sentry MCP — без новых номеров. Десятая off-phase подкатегория. Off-phase, не UI → вне R6.0/R6.1/R14 PSR_v1. promptfoo делает платные LLM-вызовы — запуск только вручную/CI, никогда в хук (конфликт-аудит ML1). Границы — ADR-007. Регулируются PSR_v1 R10.1 (Блок 1 — promptfoo dev-dep + Data Scientist skill вендорен; Блок 3 — Jupyter MCP). Установлены 17.05.2026 на ветке `worktree-a11-ml-ai-tooling`; план `docs/superpowers/plans/2026-05-17-a11-ml-ai-tooling-integration.md`.
|
||||
|
||||
### 13.3. Скоуп
|
||||
|
||||
| Тип задачи | Кто отвечает |
|
||||
|
||||
+116
-5
File diff suppressed because one or more lines are too long
@@ -0,0 +1,44 @@
|
||||
# ADR-005: Architecture-fitness enforcement via deptrac
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-05-17
|
||||
- **Deciders:** Дмитрий
|
||||
|
||||
## Context
|
||||
|
||||
Map section A6 «Архитектура систем» had tools to *record* (adr-kit #36),
|
||||
*visualize* (mermaid-skill #37) and *reference* (architecture-patterns #38)
|
||||
architecture — but nothing enforced that the code keeps matching the layered
|
||||
architecture (Controller → Service → Model …). `adr-judge` (adr-kit) enforces
|
||||
only what is hand-written as a regex in an ADR `## Enforcement` block — too
|
||||
narrow for dependency-direction rules.
|
||||
|
||||
## Decision
|
||||
|
||||
- Adopt **deptrac** (`deptrac/deptrac`, BSD-3, v4.x) — a declarative, zero-LLM
|
||||
static-analysis tool — as the layer-dependency gate, wired as lefthook
|
||||
pre-commit job 10.
|
||||
- The layer model and ruleset live in `app/deptrac.yaml` (conservative — it
|
||||
enforces only inward/upward-violating directions: a Model must not depend on
|
||||
a Service, a Service must not depend on the Http layer, etc.).
|
||||
- The first `deptrac analyse` reported **0 violations** — the codebase already
|
||||
conforms to the layer model (481 allowed cross-layer dependencies, 0
|
||||
violations) — so no baseline file is needed. If future drift is ever
|
||||
intentionally accepted, a `deptrac.baseline.yaml` can be generated then; for
|
||||
now the gate runs clean with no suppressions.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Positive: layer drift is caught at commit time, deterministically, at zero
|
||||
LLM cost (the AK6 principle adr-kit was built under).
|
||||
- Positive: deptrac's code-derived graph output doubles as a drift-proof
|
||||
C4-component diagram (`docs/architecture/c4-component-layers.md`).
|
||||
- Risk: a too-strict ruleset produces noise — mitigated by the conservative
|
||||
ruleset (verified: 0 violations against the current codebase).
|
||||
- Risk: deptrac is third-party — bus-factor; mitigated by composer-lock pinning.
|
||||
|
||||
## Enforcement
|
||||
|
||||
The layer rules live in `app/deptrac.yaml`, enforced by lefthook pre-commit
|
||||
job 10 (`deptrac analyse`) — not by an `adr-judge` regex. This ADR therefore
|
||||
carries no `adr-judge`-parsed Enforcement clause.
|
||||
@@ -0,0 +1,57 @@
|
||||
# ADR-006: A4 design-tooling boundaries
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-05-17
|
||||
- **Amended:** 2026-05-17 — Decision item 4 added (Universal Icons icon-path boundary).
|
||||
- **Deciders:** Дмитрий
|
||||
|
||||
## Context
|
||||
|
||||
The A4 «Дизайн» map section adds three tools to the existing FD #30 / UPM #31 /
|
||||
21st #32: Figma MCP (#44), Universal Icons MCP (#45), Design plugin (#46). Two
|
||||
overlaps with Frontend Design (#30) were identified during selection and must be
|
||||
closed by explicit rule, not left implicit. Figma MCP install is deferred (no
|
||||
Figma account yet); the boundary still applies the moment it is connected.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **Figma MCP — extract-only.** Figma MCP is used solely for design-data and
|
||||
design-token reads (e.g. `get_variable_defs`). Its design-to-code generation
|
||||
capability is NOT used. Frontend Design (#30) remains the sole UI solver
|
||||
(PSR_v1 R10.2 — external plugins are read-only for decisions). Figma MCP output
|
||||
is material for FD/Claude, never a substitute solver.
|
||||
2. **Design plugin a11y is design-level, pre-code.** The Design plugin's
|
||||
Accessibility Audit operates at design-critique level. Pa11y remains the single
|
||||
source of truth for technical a11y (CLAUDE.md §5 п.3, PSR_v1 R8 — Pa11y wins on
|
||||
conflict). FD continues to cover a11y-principles during design. Three tiers, no
|
||||
override.
|
||||
3. **Design plugin critique runs in R2 phase 1.** The Design plugin's Design
|
||||
Critique runs in the research / pre-code planning phase, not the phase-8 review.
|
||||
Phase-8 review stays with the PSR_v1 R5 aspect-split (FD owns the UI/UX aspect)
|
||||
plus the Superpowers review skills. The Design plugin does not replace
|
||||
`superpowers:requesting-code-review`.
|
||||
4. **Universal Icons MCP raw-SVG is for non-Lucide collections only** (amendment
|
||||
2026-05-17). Lucide is the project's branded icon set (CTO-19), rendered via the
|
||||
`lucide-vue-next` component package plus the custom Vuetify `IconSet` mapping in
|
||||
`app/resources/js/plugins/vuetify.ts` (103-entry map). For any Lucide icon that
|
||||
component path is canonical. Universal Icons MCP `get_icon` raw-SVG output is
|
||||
used only for collections `lucide-vue-next` does not provide (Heroicons, Tabler,
|
||||
Phosphor, etc.), and the SVG is wrapped into a Vue component — never inlined to
|
||||
bypass the icon system. ADR-006 originally regulated #45 only against 21st
|
||||
`logo_search`; this item closes the previously unregulated #45 ↔
|
||||
`lucide-vue-next` boundary.
|
||||
|
||||
## Consequences
|
||||
|
||||
- A Figma MCP code-generation call is a process violation (CLAUDE.md §5 п.6).
|
||||
- Universal Icons (#45) covers UI icons; 21st `logo_search` covers brand logos —
|
||||
distinct, both retained.
|
||||
- Pulling a Lucide icon as raw SVG via Universal Icons MCP, instead of
|
||||
`lucide-vue-next`, is a process violation (CLAUDE.md §5 п.6 — two tools on one
|
||||
task).
|
||||
- These boundaries are mirrored as PSR_v1 R10.1 rows + R6/R10/R14 notes; the
|
||||
Decision-4 icon-path boundary is mirrored in CLAUDE.md §3.3 #45 and Tooling §4.20.
|
||||
|
||||
## Enforcement
|
||||
|
||||
None — role boundaries, verified by code review, not by a regex gate.
|
||||
@@ -0,0 +1,39 @@
|
||||
# ADR-007: ML / AI tooling (A11)
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-05-17
|
||||
- **Deciders:** Дмитрий
|
||||
|
||||
## Context
|
||||
|
||||
The `A11 «ML / AI-разработка»` map section had zero tooling. Лидерра ships no
|
||||
ML/AI code; `calc_lead_score` is a deterministic SQL function. A toolset is needed
|
||||
for the day AI features (LLM-backed) or a scoring model are scoped.
|
||||
|
||||
## Decision
|
||||
|
||||
A11 adopts a six-position toolset in two subcategories:
|
||||
|
||||
- **LLM integration** — the claude-api skill (build), promptfoo (test prompts),
|
||||
Sentry MCP (observe). All reuse or new-light.
|
||||
- **Classical ML** — a vendored Data Scientist skill (workflow knowledge). The
|
||||
executable part, **Jupyter MCP**, is **deferred**: it needs a Python ML runtime
|
||||
the deliberately-minimal native-Windows machine lacks, and there is no model to
|
||||
train. Jupyter MCP is a reserved registry slot, installed by a separate task
|
||||
when a concrete model is scoped.
|
||||
- promptfoo runs manually / CI only — never in a hook (paid LLM calls).
|
||||
- A11 tools are non-UI → the `ml-ai-tooling` off-phase category.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Positive: A11 populated; AI features have a build+test+observe toolchain.
|
||||
- Risk: the Data Scientist skill is third-party (CC BY 4.0 content) — mitigated by
|
||||
vendoring with attribution into `.claude/skills/data-scientist/`.
|
||||
- Cost: promptfoo is a heavy devDependency (~1090 transitive packages, one native
|
||||
module). Accepted — it is dev-only tooling, not shipped to the app.
|
||||
- Deferred: no Python runtime until a model is scoped — accepted, this is the
|
||||
decision.
|
||||
|
||||
## Enforcement
|
||||
|
||||
None — A11 tools are advisory; verified by use and code review.
|
||||
@@ -0,0 +1,699 @@
|
||||
# Стартовый OpenAPI-скелет (smoke A3-интеграции, 2026-05-17).
|
||||
# Покрывает только группу /api/deals*. Полная спека REST API — отдельная задача вне scope A3.
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: Лидерра CRM — Deals API
|
||||
version: 0.1.0
|
||||
description: >
|
||||
Частичная спека REST API Лидерры. Скелет покрывает только группу /api/deals*.
|
||||
Все эндпоинты требуют аутентификации (Laravel Sanctum) и разрешают доступ
|
||||
только к сделкам текущего тенанта (RLS + middleware tenant).
|
||||
|
||||
servers:
|
||||
- url: https://app.liderra.ru
|
||||
description: Production
|
||||
|
||||
security:
|
||||
- sanctumCookie: []
|
||||
|
||||
tags:
|
||||
- name: deals
|
||||
description: Управление сделками (CRUD + bulk-операции + экспорт)
|
||||
|
||||
paths:
|
||||
/api/deals:
|
||||
get:
|
||||
operationId: deals.index
|
||||
tags: [deals]
|
||||
summary: Список сделок тенанта
|
||||
description: >
|
||||
Возвращает сделки тенанта с пагинацией. Поддерживает два режима пагинации:
|
||||
keyset (cursor) — O(1) глубины; offset-based — backward-совместимость.
|
||||
При count_only=true возвращает только {"total": N} без строк.
|
||||
parameters:
|
||||
- name: status_in[]
|
||||
in: query
|
||||
description: Фильтр по статусам (можно несколько)
|
||||
required: false
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
style: form
|
||||
explode: true
|
||||
- name: project_id
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
- name: manager_id
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
- name: search
|
||||
in: query
|
||||
description: ILIKE-поиск по phone / contact_name
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: limit
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 500
|
||||
default: 100
|
||||
- name: offset
|
||||
in: query
|
||||
description: Используется только без cursor (OFFSET-режим)
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 0
|
||||
default: 0
|
||||
- name: cursor
|
||||
in: query
|
||||
description: base64-encoded keyset cursor от предыдущей страницы
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: only_deleted
|
||||
in: query
|
||||
description: Показывать только soft-deleted (корзина)
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
- name: count_only
|
||||
in: query
|
||||
description: 'Вернуть только {"total": N}, без строк (для бейджа сайдбара)'
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
responses:
|
||||
'200':
|
||||
description: Успех
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/DealsListResponse'
|
||||
- $ref: '#/components/schemas/DealsCountResponse'
|
||||
examples:
|
||||
list:
|
||||
summary: Обычный список
|
||||
value:
|
||||
deals:
|
||||
- id: 42
|
||||
tenant_id: 1
|
||||
project_id: 5
|
||||
project_name: "B1-Москва"
|
||||
phone: "+79001234567"
|
||||
contact_name: "Иван Иванов"
|
||||
status: "new"
|
||||
manager_id: 3
|
||||
manager_name: "Мария К."
|
||||
manager_initials: "МК"
|
||||
received_at: "2026-05-17T10:00:00+03:00"
|
||||
total: 1
|
||||
offset: 0
|
||||
limit: 100
|
||||
next_cursor: null
|
||||
count_only:
|
||||
summary: count_only=true
|
||||
value:
|
||||
total: 157
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
|
||||
post:
|
||||
operationId: deals.store
|
||||
tags: [deals]
|
||||
summary: Создать сделку вручную
|
||||
description: >
|
||||
Ручное создание сделки из UI (не webhook). source_crm_id = NULL,
|
||||
баланс не списывается. Project резолвится или создаётся по project_name.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DealStoreRequest'
|
||||
example:
|
||||
project_name: "B1-Москва"
|
||||
phone: "+79001234567"
|
||||
contact_name: "Иван Иванов"
|
||||
status: "new"
|
||||
manager_id: 3
|
||||
comment: "Заинтересован, перезвонить в 15:00"
|
||||
responses:
|
||||
'201':
|
||||
description: Сделка создана
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DealStoreResponse'
|
||||
'422':
|
||||
$ref: '#/components/responses/ValidationError'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
|
||||
delete:
|
||||
operationId: deals.bulkDestroy
|
||||
tags: [deals]
|
||||
summary: Bulk soft-delete сделок
|
||||
description: Мягкое удаление нескольких сделок. Идемпотентно.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BulkIdsRequest'
|
||||
example:
|
||||
ids: [42, 43, 44]
|
||||
responses:
|
||||
'200':
|
||||
description: Результат удаления
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BulkDestroyResponse'
|
||||
example:
|
||||
deleted: 3
|
||||
requested: 3
|
||||
'422':
|
||||
$ref: '#/components/responses/ValidationError'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
|
||||
/api/deals/{id}:
|
||||
get:
|
||||
operationId: deals.show
|
||||
tags: [deals]
|
||||
summary: Детали сделки + лог активности
|
||||
description: >
|
||||
Возвращает сделку с relations и до 50 последних событий activity_log.
|
||||
Используется в DealDetailDrawer.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/DealId'
|
||||
responses:
|
||||
'200':
|
||||
description: Успех
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DealShowResponse'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
|
||||
patch:
|
||||
operationId: deals.update
|
||||
tags: [deals]
|
||||
summary: Редактировать сделку (частичное обновление)
|
||||
description: >
|
||||
Частичное обновление: comment / manager_id / status. Каждое изменение
|
||||
пишется в activity_log. NO-OP (значение не изменилось) — лог не пишется.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/DealId'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DealUpdateRequest'
|
||||
example:
|
||||
status: "in_progress"
|
||||
manager_id: 5
|
||||
comment: "Уточнить условия"
|
||||
responses:
|
||||
'200':
|
||||
description: Сделка обновлена
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DealUpdateResponse'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'422':
|
||||
$ref: '#/components/responses/ValidationError'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
|
||||
/api/deals/export:
|
||||
post:
|
||||
operationId: deals.export
|
||||
tags: [deals]
|
||||
summary: Экспорт сделок в CSV или XLSX
|
||||
description: >
|
||||
Streaming-экспорт через OpenSpout (O(1) memory). Формат по умолчанию — csv.
|
||||
CSV: UTF-8 + BOM, разделитель ;. XLSX: bold-заголовок, sheet «Сделки».
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DealExportRequest'
|
||||
example:
|
||||
ids: [42, 43, 44]
|
||||
format: csv
|
||||
responses:
|
||||
'200':
|
||||
description: Файл экспорта (streamed)
|
||||
content:
|
||||
text/csv:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
'422':
|
||||
$ref: '#/components/responses/ValidationError'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
|
||||
/api/deals/transition:
|
||||
post:
|
||||
operationId: deals.bulkTransition
|
||||
tags: [deals]
|
||||
summary: Bulk смена статуса сделок
|
||||
description: >
|
||||
Массовая смена статуса. Bulk-UPDATE + bulk-INSERT в activity_log (2 запроса
|
||||
вместо N). NO-OP (status уже совпадает) не считается.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DealTransitionRequest'
|
||||
example:
|
||||
ids: [42, 43]
|
||||
status: "in_progress"
|
||||
responses:
|
||||
'200':
|
||||
description: Результат перехода
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BulkTransitionResponse'
|
||||
example:
|
||||
updated: 2
|
||||
requested: 2
|
||||
status: "in_progress"
|
||||
'422':
|
||||
$ref: '#/components/responses/ValidationError'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
|
||||
/api/deals/restore:
|
||||
post:
|
||||
operationId: deals.bulkRestore
|
||||
tags: [deals]
|
||||
summary: Bulk восстановление soft-deleted сделок
|
||||
description: Восстановление из корзины. Идемпотентно (уже живые — NO-OP).
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BulkIdsRequest'
|
||||
example:
|
||||
ids: [42, 43]
|
||||
responses:
|
||||
'200':
|
||||
description: Результат восстановления
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BulkRestoreResponse'
|
||||
example:
|
||||
restored: 2
|
||||
requested: 2
|
||||
'422':
|
||||
$ref: '#/components/responses/ValidationError'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
sanctumCookie:
|
||||
type: apiKey
|
||||
in: cookie
|
||||
name: liderra_session
|
||||
description: Laravel Sanctum session cookie (SPA auth)
|
||||
|
||||
parameters:
|
||||
DealId:
|
||||
name: id
|
||||
in: path
|
||||
required: true
|
||||
description: ID сделки (только цифры, regex [0-9]+)
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
|
||||
schemas:
|
||||
DealSummary:
|
||||
type: object
|
||||
description: Краткое представление сделки (для списка)
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
tenant_id:
|
||||
type: integer
|
||||
project_id:
|
||||
type: integer
|
||||
project_name:
|
||||
type: [string, "null"]
|
||||
phone:
|
||||
type: string
|
||||
contact_name:
|
||||
type: [string, "null"]
|
||||
status:
|
||||
type: string
|
||||
description: Slug из таблицы lead_statuses
|
||||
manager_id:
|
||||
type: [integer, "null"]
|
||||
manager_name:
|
||||
type: [string, "null"]
|
||||
manager_initials:
|
||||
type: [string, "null"]
|
||||
received_at:
|
||||
type: [string, "null"]
|
||||
format: date-time
|
||||
|
||||
DealDetail:
|
||||
type: object
|
||||
description: Полное представление сделки (для DealDetailDrawer)
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
tenant_id:
|
||||
type: integer
|
||||
project_id:
|
||||
type: integer
|
||||
project_name:
|
||||
type: [string, "null"]
|
||||
phone:
|
||||
type: string
|
||||
contact_name:
|
||||
type: [string, "null"]
|
||||
comment:
|
||||
type: [string, "null"]
|
||||
status:
|
||||
type: string
|
||||
manager_id:
|
||||
type: [integer, "null"]
|
||||
manager_name:
|
||||
type: [string, "null"]
|
||||
manager_initials:
|
||||
type: [string, "null"]
|
||||
received_at:
|
||||
type: [string, "null"]
|
||||
format: date-time
|
||||
assigned_at:
|
||||
type: [string, "null"]
|
||||
format: date-time
|
||||
|
||||
ActivityEvent:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
event:
|
||||
type: string
|
||||
description: >
|
||||
Тип события: deal.created, deal.status_changed, deal.assigned,
|
||||
deal.commented, deal.deleted, deal.restored
|
||||
context:
|
||||
type: object
|
||||
description: Произвольный JSON-контекст (from/to/source и т.п.)
|
||||
additionalProperties: true
|
||||
created_at:
|
||||
type: [string, "null"]
|
||||
format: date-time
|
||||
actor:
|
||||
type: [object, "null"]
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
initials:
|
||||
type: string
|
||||
|
||||
DealsListResponse:
|
||||
type: object
|
||||
properties:
|
||||
deals:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DealSummary'
|
||||
total:
|
||||
type: [integer, "null"]
|
||||
description: Только в OFFSET-режиме (без cursor)
|
||||
offset:
|
||||
type: [integer, "null"]
|
||||
description: Только в OFFSET-режиме
|
||||
limit:
|
||||
type: integer
|
||||
next_cursor:
|
||||
type: [string, "null"]
|
||||
description: base64-encoded cursor для следующей страницы
|
||||
|
||||
DealsCountResponse:
|
||||
type: object
|
||||
description: Ответ при count_only=true
|
||||
properties:
|
||||
total:
|
||||
type: integer
|
||||
|
||||
DealShowResponse:
|
||||
type: object
|
||||
properties:
|
||||
deal:
|
||||
$ref: '#/components/schemas/DealDetail'
|
||||
events:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ActivityEvent'
|
||||
|
||||
DealStoreRequest:
|
||||
type: object
|
||||
required: [project_name, phone]
|
||||
properties:
|
||||
project_name:
|
||||
type: string
|
||||
maxLength: 255
|
||||
phone:
|
||||
type: string
|
||||
maxLength: 20
|
||||
contact_name:
|
||||
type: [string, "null"]
|
||||
maxLength: 255
|
||||
status:
|
||||
type: [string, "null"]
|
||||
maxLength: 50
|
||||
description: Slug из lead_statuses; по умолчанию "new"
|
||||
manager_id:
|
||||
type: [integer, "null"]
|
||||
minimum: 1
|
||||
comment:
|
||||
type: [string, "null"]
|
||||
maxLength: 5000
|
||||
|
||||
DealStoreResponse:
|
||||
type: object
|
||||
properties:
|
||||
deal:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
tenant_id:
|
||||
type: integer
|
||||
project_id:
|
||||
type: integer
|
||||
phone:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
contact_name:
|
||||
type: [string, "null"]
|
||||
manager_id:
|
||||
type: [integer, "null"]
|
||||
received_at:
|
||||
type: string
|
||||
format: date-time
|
||||
message:
|
||||
type: string
|
||||
example: "Сделка создана."
|
||||
|
||||
DealUpdateRequest:
|
||||
type: object
|
||||
description: Все поля опциональны; хотя бы одно должно присутствовать
|
||||
properties:
|
||||
comment:
|
||||
type: [string, "null"]
|
||||
maxLength: 5000
|
||||
manager_id:
|
||||
type: [integer, "null"]
|
||||
minimum: 1
|
||||
status:
|
||||
type: [string, "null"]
|
||||
maxLength: 50
|
||||
|
||||
DealUpdateResponse:
|
||||
type: object
|
||||
properties:
|
||||
deal:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
tenant_id:
|
||||
type: integer
|
||||
project_id:
|
||||
type: integer
|
||||
phone:
|
||||
type: string
|
||||
contact_name:
|
||||
type: [string, "null"]
|
||||
comment:
|
||||
type: [string, "null"]
|
||||
status:
|
||||
type: string
|
||||
manager_id:
|
||||
type: [integer, "null"]
|
||||
received_at:
|
||||
type: [string, "null"]
|
||||
format: date-time
|
||||
assigned_at:
|
||||
type: [string, "null"]
|
||||
format: date-time
|
||||
|
||||
DealExportRequest:
|
||||
type: object
|
||||
required: [ids]
|
||||
properties:
|
||||
ids:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
minItems: 1
|
||||
maxItems: 10000
|
||||
format:
|
||||
type: string
|
||||
enum: [csv, xlsx]
|
||||
default: csv
|
||||
|
||||
DealTransitionRequest:
|
||||
type: object
|
||||
required: [ids, status]
|
||||
properties:
|
||||
ids:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
minItems: 1
|
||||
maxItems: 1000
|
||||
status:
|
||||
type: string
|
||||
maxLength: 50
|
||||
description: Slug из lead_statuses
|
||||
|
||||
BulkIdsRequest:
|
||||
type: object
|
||||
required: [ids]
|
||||
properties:
|
||||
ids:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
minItems: 1
|
||||
maxItems: 1000
|
||||
|
||||
BulkTransitionResponse:
|
||||
type: object
|
||||
properties:
|
||||
updated:
|
||||
type: integer
|
||||
description: Реально изменённых (без NO-OP)
|
||||
requested:
|
||||
type: integer
|
||||
status:
|
||||
type: string
|
||||
|
||||
BulkDestroyResponse:
|
||||
type: object
|
||||
properties:
|
||||
deleted:
|
||||
type: integer
|
||||
requested:
|
||||
type: integer
|
||||
|
||||
BulkRestoreResponse:
|
||||
type: object
|
||||
properties:
|
||||
restored:
|
||||
type: integer
|
||||
requested:
|
||||
type: integer
|
||||
|
||||
ErrorMessage:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
|
||||
ValidationErrorResponse:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Не аутентифицирован
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorMessage'
|
||||
example:
|
||||
message: "Unauthenticated."
|
||||
|
||||
NotFound:
|
||||
description: Сделка не найдена
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorMessage'
|
||||
example:
|
||||
message: "Сделка не найдена."
|
||||
|
||||
ValidationError:
|
||||
description: Ошибка валидации
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationErrorResponse'
|
||||
example:
|
||||
message: "The given data was invalid."
|
||||
errors:
|
||||
status:
|
||||
- "Slug не найден в lead_statuses."
|
||||
@@ -18,6 +18,7 @@ See [ADR-000](../adr/ADR-000-adr-process.md) for the full boundary rule.
|
||||
| File | View | Mermaid type |
|
||||
|---|---|---|
|
||||
| [c4-context.md](c4-context.md) | System Context — Лидерра and its actors / external systems | `C4Context` |
|
||||
| [c4-component-layers.md](c4-component-layers.md) | Component — backend layer dependency graph (deptrac-generated, drift-proof) | `flowchart` |
|
||||
|
||||
## Regenerating
|
||||
|
||||
@@ -28,3 +29,8 @@ block. No local renderer (`mmdc`) is required.
|
||||
|
||||
For pattern guidance when shaping a new subsystem, the `architecture-patterns`
|
||||
skill covers Clean / Hexagonal / layered architecture and Domain-Driven Design.
|
||||
|
||||
The component-layer diagram ([c4-component-layers.md](c4-component-layers.md)) is
|
||||
**not** hand-authored — deptrac generates it from code
|
||||
(`cd app && php vendor/bin/deptrac analyse --formatter=mermaidjs`), so it cannot
|
||||
drift. Regenerate it after any change to the layer model in `app/deptrac.yaml`.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# C4 — Component view: backend layers
|
||||
|
||||
The Level-3 (Component) view — the dependency directions between the backend
|
||||
layers of `app/app/` (Controller, Service, Model, Job, …). Unlike the
|
||||
hand-drawn [c4-context.md](c4-context.md), this diagram is **generated from
|
||||
code** by deptrac, so it cannot drift. See [README](README.md) for the boundary
|
||||
rule and [ADR-005](../adr/ADR-005-architecture-fitness-deptrac.md).
|
||||
|
||||
```mermaid
|
||||
flowchart TD;
|
||||
Console -->|3| Service;
|
||||
Console -->|3| Model;
|
||||
Console -->|2| Job;
|
||||
Controller -->|190| Model;
|
||||
Controller -->|11| Request;
|
||||
Controller -->|8| Service;
|
||||
Controller -->|1| Mail;
|
||||
Controller -->|5| Job;
|
||||
Controller -->|6| Resource;
|
||||
Middleware -->|1| Model;
|
||||
Resource -->|1| Model;
|
||||
Job -->|75| Model;
|
||||
Job -->|28| Service;
|
||||
Job -->|4| Mail;
|
||||
Job -->|8| Exception;
|
||||
Mail -->|17| Model;
|
||||
Provider -->|2| Service;
|
||||
Repository -->|3| Model;
|
||||
Service -->|87| Model;
|
||||
Service -->|1| Repository;
|
||||
Service -->|13| Exception;
|
||||
Service -->|7| Mail;
|
||||
Service -->|5| Job;
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Edge labels are dependency counts. Every edge shown is an **allowed**
|
||||
direction — `deptrac analyse` reports **0 violations** (481 allowed
|
||||
cross-layer dependencies, 977 uncovered framework/vendor references). The
|
||||
layer model and ruleset live in `app/deptrac.yaml`; the conformance gate is
|
||||
lefthook pre-commit job 10.
|
||||
- This is the **Component** level, derived from code. To regenerate after a
|
||||
change to the layer model:
|
||||
|
||||
```bash
|
||||
cd app && php vendor/bin/deptrac analyse --formatter=mermaidjs
|
||||
```
|
||||
|
||||
- The **Context** level ([c4-context.md](c4-context.md) — external systems) is
|
||||
not code-derived and stays hand-maintained with the `mermaid` skill.
|
||||
+162
-16
@@ -228,10 +228,10 @@ function pos(ring, angleDeg) {
|
||||
|
||||
const NODES = [
|
||||
// ── ПРАВИЛА (4) ── центр + первое кольцо ───────
|
||||
{ id: 'pravila', label: 'Pravila v1.16', group: 'rules', size: 38, ring: 0, ...pos(0, 0) },
|
||||
{ id: 'claude_md', label: 'CLAUDE.md v2.2', group: 'rules', size: 34, ring: 1, ...pos(1, 30) },
|
||||
{ id: 'psr_v1', label: 'PSR_v1 v3.2', group: 'rules', size: 32, ring: 1, ...pos(1, 150) },
|
||||
{ id: 'tooling', label: 'Tooling v2.2', group: 'rules', size: 30, ring: 1, ...pos(1, 270) },
|
||||
{ id: 'pravila', label: 'Pravila v1.24', group: 'rules', size: 38, ring: 0, ...pos(0, 0) },
|
||||
{ id: 'claude_md', label: 'CLAUDE.md v2.10', group: 'rules', size: 34, ring: 1, ...pos(1, 30) },
|
||||
{ id: 'psr_v1', label: 'PSR_v1 v3.10', group: 'rules', size: 32, ring: 1, ...pos(1, 150) },
|
||||
{ id: 'tooling', label: 'Tooling v2.10', group: 'rules', size: 30, ring: 1, ...pos(1, 270) },
|
||||
|
||||
// ── ПЛАГИНЫ (13) ── второе кольцо ──────────────
|
||||
{ id: 'superpowers', label: 'Superpowers v5.1', group: 'plugins', size: 30, ring: 2, ...pos(2, 45) },
|
||||
@@ -243,14 +243,17 @@ const NODES = [
|
||||
{ id: 'claude_setup', label: 'claude-code-setup', group: 'plugins', size: 22, ring: 2, ...pos(2, 90) },
|
||||
{ id: 'plugin_dev', label: 'plugin-dev', group: 'plugins', size: 22, ring: 2, ...pos(2, 290) },
|
||||
{ id: 'context7', label: 'context7 (docs MCP)', group: 'plugins', size: 20, ring: 2, ...pos(2, 315) },
|
||||
// A6 architecture-tooling (17.05.2026) — 2 плагина раздела «Архитектура систем»
|
||||
// A6 architecture-tooling — adr-kit / architecture-patterns (плагины) + deptrac (composer dev-dep, job 10) — раздел «Архитектура систем»
|
||||
{ id: 'adr_kit', label: 'adr-kit', group: 'plugins', size: 22, ring: 2, ...pos(2, 240) },
|
||||
{ id: 'arch_patterns', label: 'architecture-patterns',group: 'plugins', size: 20, ring: 2, ...pos(2, 250) },
|
||||
{ id: 'deptrac', label: 'deptrac', group: 'plugins', size: 20, ring: 2, ...pos(2, 260) },
|
||||
// D3 audit-security (17.05.2026) — 2 плагина раздела «Аудит и управление рисками»
|
||||
{ id: 'tob_skills', label: 'Trail of Bits\nskills', group: 'plugins', size: 22, ring: 2, ...pos(2, 330) },
|
||||
{ id: 'sec_guidance', label: 'Security\nGuidance', group: 'plugins', size: 20, ring: 2, ...pos(2, 345) },
|
||||
// C9 project-management-tooling (17.05.2026) — плагин раздела «Управление проектами»
|
||||
{ id: 'product_mgmt', label: 'product-\nmanagement', group: 'plugins', size: 20, ring: 2, ...pos(2, 355) },
|
||||
// A4 design-tooling (17.05.2026) — раздел «Дизайн (UI/UX, графика, бренд)» (плагины)
|
||||
{ id: 'design_plugin', label: 'Design\nplugin', group: 'plugins', size: 20, ring: 2, ...pos(2, 155) },
|
||||
|
||||
// ── СКИЛЫ SUPERPOWERS (14) — N sector (0–90) ────
|
||||
{ id: 'sk_brainstorm', label: 'brainstorming', group: 'skills_sp', size: 18, ring: 3, ...pos(3, 5) },
|
||||
@@ -279,6 +282,10 @@ const NODES = [
|
||||
{ id: 'sk_audit_portal', label: 'audit-portal', group: 'skills_proj', size: 20, ring: 3, ...pos(3, 325) },
|
||||
// C9 project-management-tooling (17.05.2026) — вендоренный скил раздела «Управление проектами»
|
||||
{ id: 'ccpm', label: 'CCPM\n(skill)', group: 'skills_proj', size: 18, ring: 3, ...pos(3, 335) },
|
||||
// A11 ml-ai-tooling (17.05.2026) — скилы и CLI раздела «ML / AI-разработка»
|
||||
{ id: 'claude_api', label: 'claude-api\n(skill)', group: 'skills_proj', size: 18, ring: 3, ...pos(3, 345) },
|
||||
{ id: 'data_scientist', label: 'Data Scientist\n(skill)', group: 'skills_proj', size: 18, ring: 3, ...pos(3, 355) },
|
||||
{ id: 'promptfoo', label: 'promptfoo', group: 'plugins', size: 20, ring: 2, ...pos(2, 365) },
|
||||
|
||||
// ── ХУКИ (12) — S+infra + E (economy/skill) ───
|
||||
{ id: 'hk_session', label: 'SessionStart:\ncontext-inject', group: 'hooks', size: 24, ring: 4, ...pos(4, 100) },
|
||||
@@ -306,15 +313,22 @@ const NODES = [
|
||||
{ id: 'ag_pvalid', label: 'plugin-dev:\nplugin-validator',group: 'agents', size: 16, ring: 4, ...pos(4, 260) },
|
||||
{ id: 'ag_skreview', label: 'plugin-dev:\nskill-reviewer', group: 'agents', size: 16, ring: 4, ...pos(4, 275) },
|
||||
{ id: 'ag_rls', label: 'rls-reviewer', group: 'agents', size: 22, ring: 4, ...pos(4, 315) },
|
||||
// A3 integration-tooling (17.05.2026) — agent раздела «Программирование — интеграции»
|
||||
{ id: 'ag_apidocs', label: 'api-docs (agent)', group: 'agents', size: 18, ring: 4, ...pos(4, 175) },
|
||||
|
||||
// ── MCP-СЕРВЕРЫ (7) — E (UI) + W (data) ───────
|
||||
// ── MCP-СЕРВЕРЫ (9) — E (UI) + W (data) ───────
|
||||
{ id: 'mcp_21st', label: 'MCP: 21st.dev Magic', group: 'mcp', size: 20, ring: 5, ...pos(5, 130) },
|
||||
// A4 design-tooling (17.05.2026) — MCP-серверы раздела «Дизайн (UI/UX, графика, бренд)»
|
||||
{ id: 'mcp_figma', label: 'MCP: Figma\n(DEFERRED)', group: 'mcp', size: 18, ring: 5, ...pos(5, 140) },
|
||||
{ id: 'mcp_icons', label: 'MCP: Universal\nIcons', group: 'mcp', size: 18, ring: 5, ...pos(5, 120) },
|
||||
{ id: 'mcp_pw', label: 'MCP: playwright', group: 'mcp', size: 22, ring: 5, ...pos(5, 110) },
|
||||
{ id: 'mcp_gh', label: 'MCP: github', group: 'mcp', size: 22, ring: 5, ...pos(5, 75) },
|
||||
{ id: 'mcp_boost', label: 'MCP: laravel-boost', group: 'mcp', size: 24, ring: 5, ...pos(5, 290) },
|
||||
{ id: 'mcp_redis', label: 'MCP: redis', group: 'mcp', size: 22, ring: 5, ...pos(5, 310) },
|
||||
{ id: 'mcp_sentry', label: 'MCP: sentry', group: 'mcp', size: 22, ring: 5, ...pos(5, 330) },
|
||||
{ id: 'mcp_semgrep', label: 'MCP: semgrep', group: 'mcp', size: 20, ring: 5, ...pos(5, 350) },
|
||||
// A3 integration-tooling (17.05.2026) — MCP-сервер раздела «Программирование — интеграции»
|
||||
{ id: 'mcp_openapi', label: 'MCP: openapi', group: 'mcp', size: 20, ring: 5, ...pos(5, 5) },
|
||||
|
||||
// ── LEFTHOOK JOBS (10) — S+W (infra/data) ─────
|
||||
{ id: 'lh_mdlint', label: 'lefthook:\nmarkdownlint', group: 'lefthook', size: 18, ring: 5, ...pos(5, 185) },
|
||||
@@ -510,6 +524,12 @@ const EDGES = [
|
||||
E('psr_v1', 'adr_kit', 'R10.1 блок 1:\narchitecture-tooling'),
|
||||
E('psr_v1', 'arch_patterns', 'R10.1 блок 1:\narchitecture-tooling'),
|
||||
E('tooling', 'mermaid_skill', '§4.12: реестр\n(вендоренный скил)'),
|
||||
E('psr_v1', 'deptrac', 'R10.1 блок 1 note:\narchitecture-tooling'),
|
||||
|
||||
// ── A4 DESIGN-TOOLING 17.05.2026 — связи новых узлов ──
|
||||
E('psr_v1', 'design_plugin', 'R10.1 блок 1:\ndesign-tooling'),
|
||||
E('psr_v1', 'mcp_icons', 'R10.1 блок 3:\ndesign-tooling'),
|
||||
E('psr_v1', 'mcp_figma', 'R10.1 блок 3:\ndesign-tooling (DEFERRED)'),
|
||||
|
||||
// ── D3 AUDIT-SECURITY 17.05.2026 — связи новых узлов ──
|
||||
E('psr_v1', 'tob_skills', 'R10.1 блок 1:\naudit-security'),
|
||||
@@ -521,6 +541,16 @@ const EDGES = [
|
||||
E('sk_audit_portal', 'sk_regression', 'использует\nна фазе тестов'),
|
||||
CONFLICT('tob_skills', 'mcp_semgrep', 'TB1: граница разграничена — Semgrep = inline SAST, Trail of Bits = глубокие on-demand аудит-кампании. Параллельное использование разрешено при разных сценариях.', 'GREEN'),
|
||||
|
||||
// ── A3 INTEGRATION-TOOLING 17.05.2026 — связи новых узлов ──
|
||||
E('psr_v1', 'mcp_openapi', 'R10.1 блок 3:\nintegration-tooling'),
|
||||
E('tooling', 'mcp_openapi', '§4.22 #47 — реестр'),
|
||||
E('ag_apidocs', 'mcp_openapi', 'спека → MCP-ресурс'),
|
||||
|
||||
// ── A11 ML-AI-TOOLING 17.05.2026 — связи новых узлов ──
|
||||
E('psr_v1', 'promptfoo', 'R10.1 блок 1:\nml-ai-tooling'),
|
||||
E('tooling', 'claude_api', 'reuse — built-in skill\n(PSR_v1 R10.1 блок 2)'),
|
||||
E('tooling', 'data_scientist', '§4.24 #49 — реестр'),
|
||||
|
||||
// ══════════════════════════════════════════════════
|
||||
// КОНФЛИКТЫ — 3-color classification (iter2 §4)
|
||||
// 🔴 не закрыт правилом / ⚫ возник на практике / 🟢 закрыт правилом
|
||||
@@ -611,7 +641,7 @@ const NODE_DETAILS = {
|
||||
'Править можно только через скил `/claude-md-management:claude-md-improver` или `:revise-claude-md` (правило §5 п.10). Прямые Edit/Write блокируются хуком предупреждения.',
|
||||
[{ name: 'Pravila', cond: 'всегда подчинён (уровень 2a)' }],
|
||||
[
|
||||
{ name: 'Tooling v2.2', cond: 'ссылается как на реестр инструментов' },
|
||||
{ name: 'Tooling v2.10', cond: 'ссылается как на реестр инструментов' },
|
||||
{ name: 'плагин claude-md-management', cond: 'правило §5 п.10 — единственный канал правок' }
|
||||
],
|
||||
[
|
||||
@@ -634,7 +664,7 @@ const NODE_DETAILS = {
|
||||
[{ name: 'CLAUDE.md', desc: 'CLAUDE.md §5 п.10 требует править только через скил claude-md-management, а PSR_v1 это ограничение не повторяет — риск прямых Edit', type: 'GREEN' }]
|
||||
),
|
||||
tooling: nd(
|
||||
'Реестр 55 позиций — 35 формализованных инструментов + 20 ruflo-плагинов; §4.10 — ruflo как advisory/automation-подсистема. Когда что использовать, команды установки, конфликты.',
|
||||
'Реестр 70 позиций — 50 формализованных инструментов + 20 ruflo-плагинов; §4.10 — ruflo как advisory/automation-подсистема. Когда что использовать, команды установки, конфликты.',
|
||||
'При выборе инструмента для фазы (нулевая документация / первая backend / вторая frontend / третья перед запуском в боевую среду), при добавлении нового инструмента, при обновлении версий.',
|
||||
'При прямом конфликте с CLAUDE.md побеждает CLAUDE.md (оперативная карта уровня 2a). Любая правка требует синхронизации с CLAUDE.md §3.',
|
||||
[
|
||||
@@ -724,6 +754,40 @@ const NODE_DETAILS = {
|
||||
[],
|
||||
[{ name: 'docs/architecture/', cond: 'C4-диаграммы → c4-context.md' }]
|
||||
),
|
||||
deptrac: nd(
|
||||
'Composer dev-dependency deptrac/deptrac v4.6.1 (BSD-3) — статический анализ направления зависимостей между слоями App\\ (Controller/Service/Model/Job/…). Чистый PHP, 0 вызовов LLM.',
|
||||
'Архитектурный fitness-гейт: проверяет, что код не нарушает границы слоёв. Конфиг app/deptrac.yaml (13 слоёв) + ruleset; запускается автоматически как lefthook pre-commit job 10 на staged app/**/*.php.',
|
||||
'Правило PSR_v1 R10.1 блок 1 note (architecture-tooling, off-phase — composer dev-dep, не marketplace-плагин). Первый прогон 0 нарушений → baseline-файл не нужен (red-green доказан). Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.18, CLAUDE.md §3.3 #43.',
|
||||
[{ name: 'PSR_v1', cond: 'R10.1 блок 1 note: architecture-tooling' }, { name: 'Tooling', cond: '§4.18 #43 — реестр' }],
|
||||
[{ name: 'lefthook job 10 (deptrac)', cond: 'врезан как pre-commit гейт направления зависимостей' }],
|
||||
[{ name: 'docs/architecture/', cond: 'mermaidjs-форматтер → c4-component-layers.md' }]
|
||||
),
|
||||
|
||||
// ── A4 DESIGN-TOOLING (17.05.2026) ──────────────
|
||||
mcp_figma: nd(
|
||||
'MCP Figma (#44) — DEFERRED, precondition: Figma-аккаунт. Extract-only (ADR-006): извлечение токенов/variables из источника дизайна. FD #30 остаётся единственным UI-решателем.',
|
||||
'При наличии Figma-аккаунта и нужде извлечь дизайн-токены/variables напрямую из Figma-файла.',
|
||||
'DEFERRED — не установлен, требует Figma-аккаунт (Б-1). Не UI-решатель → вне R6.0/R6.1/R14. Extract-only, не генерирует UI-решения. PSR_v1 R10.1 блок 3.',
|
||||
[{ name: 'PSR_v1', cond: 'R10.1 блок 3: design-tooling (DEFERRED)' }],
|
||||
[],
|
||||
[{ name: 'Frontend Design', cond: 'FD остаётся единственным UI-решателем; Figma MCP — только источник токенов' }]
|
||||
),
|
||||
mcp_icons: nd(
|
||||
'MCP Universal Icons (#45) — поиск/вставка SVG-иконок, 10 коллекций включая Lucide. Tools search_icons/get_icon. SVG framework-neutral (R6.0).',
|
||||
'При поиске иконки из коллекции Lucide или других (Heroicons, Tabler, Phosphor и др.) — получить SVG-исходник для вставки в Vue-компонент.',
|
||||
'SVG framework-neutral — результат нужно обернуть в Vue-компонент вручную. R6.0 фильтр не блокирует (SVG — не React/Tailwind). Lucide — branded иконочный стек проекта (CLAUDE.md §2). PSR_v1 R10.1 блок 3.',
|
||||
[{ name: 'PSR_v1', cond: 'R10.1 блок 3: design-tooling' }],
|
||||
[],
|
||||
[{ name: 'Frontend Design', cond: 'FD использует результат поиска как материал при UI-задачах' }]
|
||||
),
|
||||
design_plugin: nd(
|
||||
'Плагин Design (#46, Anthropic Verified) — дизайн-критика, a11y-аудит дизайн-уровня, UX-копирайт, research synthesis. Pre-code (ADR-006); Pa11y остаётся техническим a11y SoT.',
|
||||
'При дизайн-критике макета или компонента, при UX-анализе flow, при написании UX-копирайта, при synthesis пользовательских исследований.',
|
||||
'Pre-code инструмент (ADR-006) — не генерирует финальный код, даёт дизайн-рекомендации. Pa11y остаётся техническим a11y SoT (§5 п.3). Не UI-решатель в смысле PSR_v1 R14 → вне R14 pipeline. PSR_v1 R10.1 блок 1.',
|
||||
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: design-tooling' }],
|
||||
[],
|
||||
[{ name: 'Frontend Design', cond: 'Design plugin — pre-code критика; FD — post-spec UI-решатель; разные фазы' }]
|
||||
),
|
||||
|
||||
// ── C9 PROJECT-MANAGEMENT-TOOLING (17.05.2026) ──
|
||||
ccpm: nd(
|
||||
@@ -778,6 +842,50 @@ const NODE_DETAILS = {
|
||||
[{ name: 'скил security-review', cond: 'пара в D3 audit-security' }]
|
||||
),
|
||||
|
||||
// ── A3 INTEGRATION-TOOLING (17.05.2026) ──────────
|
||||
ag_apidocs: nd(
|
||||
'Агент claude-flow — генерирует OpenAPI-спеку REST API по роутам и контроллерам Laravel. Pattern learning. 0 установки — агент доступен в сессии.',
|
||||
'При фиксации контракта REST API: генерация/обновление OpenAPI-спеки группы эндпоинтов. Результат — docs/api/.',
|
||||
'Sub-агент claude-flow — узел карты, но без отдельного номера в реестре Tooling Прил. Н (реестр — plugin-grain; 11 agent-узлов карты так же без Tooling-номеров). Не UI → вне фильтров R6.0/R6.1/R14.',
|
||||
[{ name: 'CLAUDE.md', cond: '§3.3 — упомянут при #47 openapi-mcp' }],
|
||||
[],
|
||||
[{ name: 'MCP: openapi', cond: 'генерирует спеку → openapi-mcp отдаёт её как MCP-ресурс' }]
|
||||
),
|
||||
mcp_openapi: nd(
|
||||
'MCP-сервер (npm, stdio) — отдаёт OpenAPI-спеку как MCP-ресурс/тулы; introspection своей и чужих API при интеграционной разработке.',
|
||||
'При работе с интеграциями (API/вебхуки) — обращение к структуре OpenAPI-спеки из сессии Claude. READ-ONLY introspection.',
|
||||
'Правило PSR_v1 R10.1 блок 3 (integration-tooling, off-phase — 9-я подкатегория). stdio-режим, без port-conflict. Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.22 #47, CLAUDE.md §3.3 #47.',
|
||||
[{ name: 'PSR_v1', cond: 'R10.1 блок 3: integration-tooling' }, { name: 'Tooling', cond: '§4.22 #47 — реестр' }],
|
||||
[],
|
||||
[{ name: 'docs/api/', cond: 'источник OpenAPI-спеки' }]
|
||||
),
|
||||
|
||||
// ── A11 ML-AI-TOOLING (17.05.2026) ──────────────
|
||||
claude_api: nd(
|
||||
'Скил сборки AI-фич на Anthropic SDK (prompt-кэш). Reuse — раздел A11 опирается также на context7 MCP (доки) и Sentry MCP (LLM-наблюдаемость).',
|
||||
'При разработке AI-фич на Anthropic API / Claude SDK — скил задаёт паттерны prompt-кэша, batch-запросов, tool use.',
|
||||
'Reuse-узел раздела A11 (ml-ai-tooling). claude-api — встроенный скил Claude Code, не нумерованная Tooling-позиция; регистрация — Tooling «built-in skills» + PSR_v1 R10.1 блок 2. В A11 — reuse-слой (CLAUDE.md §6). Не UI → вне фильтров R6.0/R6.1/R14.',
|
||||
[{ name: 'Tooling', cond: 'built-in skill — PSR_v1 R10.1 блок 2 (reuse)' }],
|
||||
[],
|
||||
[{ name: 'context7 MCP', cond: 'документация Anthropic SDK' }, { name: 'Sentry MCP', cond: 'LLM-наблюдаемость (off-phase reuse)' }]
|
||||
),
|
||||
promptfoo: nd(
|
||||
'npm-CLI eval LLM-промптов: ассерты, регрессия, red-team. Запуск вручную/CI — не в хуках (платные LLM-вызовы).',
|
||||
'При разработке и проверке AI-промптов — запуск test-suite promptfoo вручную или в CI. Никогда в pre-commit хук (ML1: платные вызовы).',
|
||||
'Правило PSR_v1 R10.1 блок 1 note (ml-ai-tooling, off-phase). npm devDependency, тяжёлый (~1090 пакетов). Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.23 #48, CLAUDE.md §3.3 #48.',
|
||||
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: ml-ai-tooling' }],
|
||||
[{ name: 'ML1', cond: 'никогда в хук/pre-commit — платные LLM-вызовы' }],
|
||||
[]
|
||||
),
|
||||
data_scientist: nd(
|
||||
'Vendored-скил: классический ML-воркфлоу — выбор алгоритма, feature engineering, оценка модели.',
|
||||
'При ML-задаче (выбор алгоритма, feature engineering, валидация модели) — knowledge-only playbook без генерации кода.',
|
||||
'Вендорен в .claude/skills/data-scientist/ (ML3 — lefthook markdownlint+cspell исключают через job-exclude). Knowledge-only, не решатель. Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.24 #49, CLAUDE.md §3.3 #49.',
|
||||
[{ name: 'Tooling', cond: '§4.24 #49 — реестр' }],
|
||||
[],
|
||||
[]
|
||||
),
|
||||
|
||||
// ── СКИЛЫ SUPERPOWERS ────────────────────────────
|
||||
sk_brainstorm: nd(
|
||||
'Продумывает задачу вместе с заказчиком, формулирует варианты A/B/C и согласует дизайн до написания кода.',
|
||||
@@ -1721,10 +1829,10 @@ const META_WINDOW = '09–16.05.2026'; // окно подсчёта испо
|
||||
// usesSrc: 'скил' | 'агент' | 'MCP' | 'хук' | 'memory-чтение' | 'коммиты' | 'инспекция' | '—'
|
||||
const NODE_META = {
|
||||
// ── ПРАВИЛА (4) — узлы-правила, напрямую не вызываются ──
|
||||
pravila: { since: '06.05.2026', changed: '16.05.2026', uses: null, usesSrc: '—' },
|
||||
claude_md: { since: '06.05.2026', changed: '16.05.2026', uses: null, usesSrc: '—' },
|
||||
psr_v1: { since: '09.05.2026', changed: '16.05.2026', uses: null, usesSrc: '—' },
|
||||
tooling: { since: '06.05.2026', changed: '16.05.2026', uses: null, usesSrc: '—' },
|
||||
pravila: { since: '06.05.2026', changed: '17.05.2026', uses: null, usesSrc: '—' },
|
||||
claude_md: { since: '06.05.2026', changed: '17.05.2026', uses: null, usesSrc: '—' },
|
||||
psr_v1: { since: '09.05.2026', changed: '17.05.2026', uses: null, usesSrc: '—' },
|
||||
tooling: { since: '06.05.2026', changed: '17.05.2026', uses: null, usesSrc: '—' },
|
||||
|
||||
// ── ПЛАГИНЫ (5) ──
|
||||
superpowers: { since: '09.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
@@ -1855,6 +1963,7 @@ const NODE_META = {
|
||||
adr_kit: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
arch_patterns: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
mermaid_skill: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
deptrac: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
|
||||
// ── D3 AUDIT-SECURITY 17.05.2026 ──
|
||||
tob_skills: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
@@ -1865,6 +1974,20 @@ const NODE_META = {
|
||||
// ── C9 PROJECT-MANAGEMENT-TOOLING 17.05.2026 ──
|
||||
ccpm: { since: '17.05.2026', changed: '—', uses: null, usesSrc: 'скил' },
|
||||
product_mgmt: { since: '17.05.2026', changed: '—', uses: null, usesSrc: 'плагин' },
|
||||
|
||||
// ── A4 DESIGN-TOOLING 17.05.2026 ──
|
||||
mcp_figma: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
mcp_icons: { since: '17.05.2026', changed: '—', uses: null, usesSrc: 'MCP' },
|
||||
design_plugin:{ since: '17.05.2026', changed: '—', uses: null, usesSrc: 'плагин' },
|
||||
|
||||
// ── A3 INTEGRATION-TOOLING (17.05.2026) ──
|
||||
ag_apidocs: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
mcp_openapi: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
|
||||
// ── A11 ML-AI-TOOLING (17.05.2026) ──
|
||||
claude_api: { since: '17.05.2026', changed: '—', uses: null, usesSrc: 'скил' },
|
||||
promptfoo: { since: '17.05.2026', changed: '—', uses: null, usesSrc: 'CLI' },
|
||||
data_scientist: { since: '17.05.2026', changed: '—', uses: null, usesSrc: 'скил' },
|
||||
};
|
||||
|
||||
// Явные парные дубли (Фича 3) — попадают в кнопку «⧉ Дубли».
|
||||
@@ -1947,7 +2070,7 @@ const SECTIONS = [
|
||||
{ id: 'E7', bucket: 'E', label: 'Исследования' },
|
||||
{ id: 'E8', bucket: 'E', label: 'Самообучение Claude' },
|
||||
];
|
||||
// Узел -> раздел. Покрывает все 112 узлов карты.
|
||||
// Узел -> раздел. Покрывает все 121 узлов карты.
|
||||
const NODE_SECTION = {
|
||||
// правила (4)
|
||||
pravila: 'E1', claude_md: 'E1', psr_v1: 'E1', tooling: 'E1',
|
||||
@@ -1989,12 +2112,28 @@ const NODE_SECTION = {
|
||||
sk_regression: 'A5',
|
||||
mem_audit_b: 'E4', mem_audit_c: 'E4', mem_suppliercrm: 'E4', mem_audit12: 'E4',
|
||||
mem_audit14: 'E4', mem_sprint1: 'E4', mem_sprint2: 'E4', mem_sprint3: 'E4',
|
||||
// A6 architecture-tooling 17.05.2026 — раздел «Архитектура систем» наполнен
|
||||
adr_kit: 'A6', arch_patterns: 'A6', mermaid_skill: 'A6',
|
||||
// A6 architecture-tooling 17.05.2026 — раздел «Архитектура систем» наполнен (+deptrac)
|
||||
adr_kit: 'A6', arch_patterns: 'A6', mermaid_skill: 'A6', deptrac: 'A6',
|
||||
// D3 audit-security 17.05.2026 — раздел «Аудит и управление рисками» наполнен
|
||||
tob_skills: 'D3', sec_guidance: 'D3', sk_security_review: 'D3', sk_audit_portal: 'D3',
|
||||
// C9 project-management-tooling 17.05.2026 — раздел «Управление проектами» наполнен
|
||||
ccpm: 'C9', product_mgmt: 'C9',
|
||||
// A4 design-tooling 17.05.2026 — раздел «Дизайн (UI/UX, графика, бренд)» расширен (3→6 узлов)
|
||||
mcp_figma: 'A4', mcp_icons: 'A4', design_plugin: 'A4',
|
||||
// A3 integration-tooling 17.05.2026 — раздел «Программирование — интеграции» наполнен
|
||||
ag_apidocs: 'A3', mcp_openapi: 'A3',
|
||||
// A11 ml-ai-tooling 17.05.2026 — раздел «ML / AI-разработка» наполнен
|
||||
claude_api: 'A11', promptfoo: 'A11', data_scientist: 'A11',
|
||||
};
|
||||
// Вторичная классификация: узел первично в NODE_SECTION, дополнительно — в этих
|
||||
// разделах (кросс-реф). Введено A3-интеграцией 17.05.2026 — раздел A3 наполняется
|
||||
// частично кросс-реф существующих интеграционных инструментов. NODE_SECTION 1:1 не трогается.
|
||||
const NODE_SECTION_SECONDARY = {
|
||||
mcp_boost: ['A3'],
|
||||
context7: ['A3'],
|
||||
ag_pest: ['A3'],
|
||||
mcp_semgrep: ['A3'],
|
||||
mcp_sentry: ['A3'],
|
||||
};
|
||||
// Производные индексы для рендера панели и Паспорта.
|
||||
const SECTION_BY_ID = new Map(SECTIONS.map(s => [s.id, s]));
|
||||
@@ -2002,6 +2141,9 @@ const SECTION_NODES = new Map(SECTIONS.map(s => [s.id, []]));
|
||||
NODES.forEach(n => {
|
||||
const sid = NODE_SECTION[n.id];
|
||||
if (sid && SECTION_NODES.has(sid)) SECTION_NODES.get(sid).push(n.id);
|
||||
(NODE_SECTION_SECONDARY[n.id] || []).forEach(secId => {
|
||||
if (SECTION_NODES.has(secId)) SECTION_NODES.get(secId).push(n.id);
|
||||
});
|
||||
});
|
||||
|
||||
// ════════════════════════════════════════════════════
|
||||
@@ -2108,7 +2250,11 @@ function showNodeLegend(nodeId) {
|
||||
document.getElementById('ld-since').textContent = meta.since || '—';
|
||||
document.getElementById('ld-changed').textContent = meta.changed || '—';
|
||||
const _sec = NODE_SECTION[nodeId] ? SECTION_BY_ID.get(NODE_SECTION[nodeId]) : null;
|
||||
document.getElementById('ld-section').textContent = _sec ? `${_sec.id} · ${_sec.label}` : '—';
|
||||
const _secExtra = (NODE_SECTION_SECONDARY[nodeId] || [])
|
||||
.map(id => SECTION_BY_ID.get(id)).filter(Boolean);
|
||||
let _secText = _sec ? `${_sec.id} · ${_sec.label}` : '—';
|
||||
if (_secExtra.length) _secText += ` (+${_secExtra.map(s => s.id).join(', ')})`;
|
||||
document.getElementById('ld-section').textContent = _secText;
|
||||
|
||||
const usesEl = document.getElementById('ld-uses');
|
||||
if (meta.uses === null || meta.uses === undefined) {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# docs/ml — ML / AI playbook (map section A11)
|
||||
|
||||
Home of the `A11 «ML / AI-разработка»` section. Defines the tooling Лидерра uses
|
||||
to build and test ML/AI capability. The portal currently ships no ML/AI code —
|
||||
this section is the toolset, ready for when AI features are scoped.
|
||||
|
||||
## Toolset
|
||||
|
||||
| Tool | Role | Status |
|
||||
|---|---|---|
|
||||
| **claude-api skill** | Build AI features on the Anthropic SDK (lead qualification, call summaries, email drafts) with prompt caching. | reuse — already available |
|
||||
| **context7 MCP** | Up-to-date docs for AI/ML libraries and SDKs. | reuse — already installed |
|
||||
| **Sentry MCP** | Debug AI features in production via Sentry AI/LLM monitoring (read-only). | reuse — Tooling #34, pending the Sentry deployment (Б-1) |
|
||||
| **promptfoo** | Test suite for LLM prompts/agents: assertions, regression, LLM-graded eval, red-team. | installed — `npx promptfoo` |
|
||||
| **Data Scientist skill** | Classical-ML workflow: business objective → ML task, algorithm selection, feature engineering, evaluation. | installed — vendored skill |
|
||||
| **Jupyter MCP** | Executable notebooks for real model training. | **deferred** — see below |
|
||||
|
||||
## Boundaries (which tool for which job)
|
||||
|
||||
- **Building an AI feature** (a prompt-backed endpoint) → the **claude-api skill**.
|
||||
- **Testing / regression-checking an LLM prompt** → **promptfoo** (`docs/ml/promptfoo-example/`).
|
||||
- **A classical-ML modelling question** (which algorithm, how to evaluate) → the
|
||||
**Data Scientist skill** (`.claude/skills/data-scientist/`).
|
||||
- **Executing a notebook / training a model** → **Jupyter MCP** — *deferred*.
|
||||
- promptfoo's **red-team** tests *prompts*; the D3 Trail of Bits / Semgrep tools do
|
||||
SAST of *code*. Different objects — not a duplication.
|
||||
|
||||
## promptfoo — running an eval
|
||||
|
||||
promptfoo makes **paid** Anthropic API calls. It runs **manually or in CI only** —
|
||||
never in a git hook, never in pre-commit, never automatically.
|
||||
|
||||
- API key: `ANTHROPIC_API_KEY` env var (PowerShell User scope — the Sentry
|
||||
`SENTRY_AUTH_TOKEN` pattern). Never commit a key.
|
||||
- Run the seed example: `npm run eval:llm` (or
|
||||
`npx promptfoo eval -c docs/ml/promptfoo-example/promptfooconfig.yaml`).
|
||||
- Footprint note: promptfoo is a large devDependency (~1090 transitive packages,
|
||||
one native module — `better-sqlite3` — which `prebuild-install` fetches as a
|
||||
prebuilt binary; no local C++ toolchain is required when the prebuild download
|
||||
succeeds). It is dev-tooling only — not shipped to the Laravel app.
|
||||
|
||||
## Jupyter MCP — why deferred
|
||||
|
||||
Jupyter MCP executes notebooks; it needs a Python ML environment (pandas /
|
||||
scikit-learn / Jupyter). The machine is native Windows, deliberately runtime-minimal
|
||||
(no Docker), and there is no model to train yet. Jupyter MCP is a **reserved slot**:
|
||||
registered in the Tooling registry as *pending*, installed by a separate severable
|
||||
task when a concrete ML model is scoped. See the A11 plan's "Deferred Task".
|
||||
@@ -0,0 +1,20 @@
|
||||
# promptfoo example — lead-qualification eval
|
||||
|
||||
A worked promptfoo eval: a HOT/WARM/COLD lead-classification prompt with three
|
||||
assertion cases. Demonstrates the A11 prompt-testing workflow.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
# from the repo root; ANTHROPIC_API_KEY must be set (PowerShell User scope)
|
||||
npm run eval:llm
|
||||
```
|
||||
|
||||
This makes **paid** Anthropic API calls. Run it manually or in CI only — never
|
||||
in a git hook or pre-commit (A11 rule ML1). See `docs/ml/README.md`.
|
||||
|
||||
## Adapt
|
||||
|
||||
Copy `promptfooconfig.yaml` next to a real prompt when an AI feature is built.
|
||||
Swap the model, add `tests`, use richer assertions (`contains`, `llm-rubric`,
|
||||
cost/latency thresholds). Full reference: <https://promptfoo.dev/docs/>.
|
||||
@@ -0,0 +1,31 @@
|
||||
# yaml-language-server: $schema=https://promptfoo.dev/config-schema.json
|
||||
# Seed example — A11. Lead-qualification prompt eval.
|
||||
# Run manually: npm run eval:llm (needs ANTHROPIC_API_KEY — never in CI/hooks)
|
||||
description: "Лидерра — lead-qualification prompt eval (example)"
|
||||
|
||||
prompts:
|
||||
- |
|
||||
Классифицируй обращение лида как HOT, WARM или COLD.
|
||||
Ответь РОВНО одним словом — HOT, WARM или COLD.
|
||||
|
||||
Обращение: {{message}}
|
||||
|
||||
providers:
|
||||
- id: anthropic:messages:claude-haiku-4-5-20251001
|
||||
|
||||
tests:
|
||||
- vars:
|
||||
message: "Нужно срочно, бюджет согласован, готовы подписать договор сегодня."
|
||||
assert:
|
||||
- type: equals
|
||||
value: HOT
|
||||
- vars:
|
||||
message: "Интересно, расскажите подробнее про условия и сроки."
|
||||
assert:
|
||||
- type: equals
|
||||
value: WARM
|
||||
- vars:
|
||||
message: "Просто смотрю что есть на рынке, ничего конкретного."
|
||||
assert:
|
||||
- type: equals
|
||||
value: COLD
|
||||
@@ -0,0 +1,240 @@
|
||||
# Sprint 3E — Settings placeholder-tabs (D6/D7) Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Убрать из `SettingsView` 4 placeholder-вкладки («Проекты», «Команда», «Интеграции», «Тихие часы»), которые показывают «В разработке» — UI не должен обещать нереализованный функционал.
|
||||
|
||||
**Architecture:** Чистое удаление. `SettingsView` оставляет 4 рабочие вкладки (Профиль, Безопасность, API и Webhook, Уведомления). Компонент `PlaceholderTab.vue` удаляется целиком. Spec-тест приводится к 4-вкладочному состоянию + добавляется регрессионная проверка, что placeholder'ы пропали.
|
||||
|
||||
**Tech Stack:** Vue 3 (`<script setup>` + TypeScript), Vuetify 3, Vitest 4 + @vue/test-utils.
|
||||
|
||||
---
|
||||
|
||||
## Контекст и per-tab решение (audit D6/D7)
|
||||
|
||||
Аудит портала ([docs/superpowers/specs/2026-05-15-portal-audit-design.md](../specs/2026-05-15-portal-audit-design.md)):
|
||||
|
||||
- **D6** — «PlaceholderTab × 4 — реализовать или скрыть (decide per-tab)».
|
||||
- **D7** — «SettingsView left-rail: 8 tab'ов, 4 заглушки — Hide-if-not-implemented».
|
||||
|
||||
**Per-tab решение — скрыть все 4** (реализация каждой = отдельный эпик, вне scope Sprint 3E):
|
||||
|
||||
| Вкладка | Решение | Обоснование |
|
||||
|---|---|---|
|
||||
| Проекты | скрыть | Полноценный `/projects` view уже есть — вкладка чистый дубль. |
|
||||
| Команда | скрыть | Нет ни `/team`-маршрута, ни backend; реализация = отдельный L-эпик со schema-работой, не в графике спринтов. |
|
||||
| Интеграции | скрыть | Telegram/1С/JivoSite/Yandex SSO — все внешне-блокированы (Б-1 и пр.). |
|
||||
| Тихие часы | скрыть | `quiet_hours` отсутствует в `db/schema.sql`; ТЗ §17.8 спецификацию даёт, но колонок/backend нет — отдельный эпик. |
|
||||
|
||||
«Импорт»-вкладка из предложения D7 — это H8 (Sprint 4, миграция §6), **вне scope Sprint 3E**.
|
||||
|
||||
Скрытие не отменяет ТЗ-требования (Команда / Тихие часы §17.8) — вкладки вернутся при реальной реализации соответствующих модулей.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Modify: `app/resources/js/views/SettingsView.vue` — убрать 4 placeholder-вкладки, `placeholderProps` computed, импорт и использование `PlaceholderTab`, неиспользуемый импорт `computed`; обновить docblock.
|
||||
- Delete: `app/resources/js/views/settings/PlaceholderTab.vue` — компонент больше не используется.
|
||||
- Test: `app/tests/Frontend/SettingsView.spec.ts` — 8 → 4 вкладки, убрать placeholder-тест, добавить регрессию.
|
||||
|
||||
**НЕ трогать:** `app/dev-indices.json` (авто-генерируемый временной DevIndex-фичей, уже `M` в git status — не стейджить, не коммитить); `SettingsView.story.vue` (ссылается только на `SettingsView`, не на `PlaceholderTab` — изменений не требует).
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Скрыть 4 placeholder-вкладки в SettingsView
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `app/resources/js/views/SettingsView.vue`
|
||||
- Delete: `app/resources/js/views/settings/PlaceholderTab.vue`
|
||||
- Test: `app/tests/Frontend/SettingsView.spec.ts`
|
||||
|
||||
- [ ] **Step 1: Привести spec-тест к 4-вкладочному состоянию (failing test first)**
|
||||
|
||||
Заменить весь файл `app/tests/Frontend/SettingsView.spec.ts` на:
|
||||
|
||||
```ts
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { createPinia } from 'pinia';
|
||||
import { createVuetify } from 'vuetify';
|
||||
import SettingsView from '../../resources/js/views/SettingsView.vue';
|
||||
|
||||
describe('SettingsView.vue', () => {
|
||||
const factory = () =>
|
||||
mount(SettingsView, {
|
||||
global: { plugins: [createPinia(), createVuetify()] },
|
||||
});
|
||||
|
||||
it('монтируется и содержит заголовок «Настройки»', () => {
|
||||
const wrapper = factory();
|
||||
expect(wrapper.find('h1').text()).toBe('Настройки');
|
||||
});
|
||||
|
||||
it('содержит ровно 4 nav-tabs (placeholder-вкладки убраны, audit D6/D7)', () => {
|
||||
const wrapper = factory();
|
||||
const items = wrapper.findAll('.tabs-rail .v-list-item');
|
||||
expect(items.length).toBe(4);
|
||||
});
|
||||
|
||||
it('содержит все 4 названия рабочих вкладок', () => {
|
||||
const wrapper = factory();
|
||||
const text = wrapper.text();
|
||||
const labels = ['Профиль', 'Безопасность', 'API и Webhook', 'Уведомления'];
|
||||
labels.forEach((l) => expect(text).toContain(l));
|
||||
});
|
||||
|
||||
it('не содержит placeholder-вкладок и текста «В разработке»', () => {
|
||||
const wrapper = factory();
|
||||
const railText = wrapper.find('.tabs-rail').text();
|
||||
['Команда', 'Интеграции', 'Тихие часы'].forEach((l) => expect(railText).not.toContain(l));
|
||||
expect(wrapper.text()).not.toContain('В разработке');
|
||||
});
|
||||
|
||||
it('по умолчанию показывает вкладку «Профиль»', () => {
|
||||
const wrapper = factory();
|
||||
const text = wrapper.text();
|
||||
// ProfileTab содержит поля Имя / Фамилия (split из «Полное имя» в audit D1) и Тайм-зона.
|
||||
expect(text).toContain('Имя');
|
||||
expect(text).toContain('Фамилия');
|
||||
expect(text).toContain('Тайм-зона');
|
||||
});
|
||||
|
||||
it('переключение на «Уведомления» показывает матрицу 8×3', async () => {
|
||||
const wrapper = factory();
|
||||
const items = wrapper.findAll('.tabs-rail .v-list-item');
|
||||
const notifItem = items.find((i) => i.text().includes('Уведомления'));
|
||||
await notifItem!.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
const text = wrapper.text();
|
||||
expect(text).toContain('События × каналы');
|
||||
// 8 типов событий из schema users.notification_preferences.
|
||||
['Новый лид', 'Напоминание', 'Низкий баланс', 'Нулевой баланс', 'Анонсы и промо'].forEach((e) =>
|
||||
expect(text).toContain(e),
|
||||
);
|
||||
});
|
||||
|
||||
it('переключение на «Безопасность» показывает 2FA и сессии', async () => {
|
||||
const wrapper = factory();
|
||||
const items = wrapper.findAll('.tabs-rail .v-list-item');
|
||||
const secItem = items.find((i) => i.text().includes('Безопасность'));
|
||||
await secItem!.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
const text = wrapper.text();
|
||||
expect(text).toContain('Двухфакторная авторизация');
|
||||
expect(text).toContain('Активные сессии');
|
||||
});
|
||||
|
||||
it('переключение на «API и Webhook» показывает API-ключ и signing secret', async () => {
|
||||
const wrapper = factory();
|
||||
const items = wrapper.findAll('.tabs-rail .v-list-item');
|
||||
const apiItem = items.find((i) => i.text().includes('API'));
|
||||
await apiItem!.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
const text = wrapper.text();
|
||||
expect(text).toContain('API-ключ');
|
||||
expect(text).toContain('Signing secret');
|
||||
expect(text).toContain('HMAC');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Изменения относительно текущего файла: тест «ровно 8 nav-tabs» → 4; «8 названий вкладок» → 4 рабочих; тест «placeholder-вкладки показывают „В разработке"» удалён, вместо него — регрессия «не содержит placeholder-вкладок».
|
||||
|
||||
- [ ] **Step 2: Прогнать тест — убедиться, что падает**
|
||||
|
||||
Run: `cd app && npm run test:vue -- --run SettingsView`
|
||||
Expected: FAIL — текущий `SettingsView.vue` рендерит 8 вкладок, тесты «ровно 4 nav-tabs» и «не содержит placeholder-вкладок» красные.
|
||||
|
||||
- [ ] **Step 3: Удалить 4 placeholder-вкладки из `SettingsView.vue`**
|
||||
|
||||
Заменить блок `<script setup>` (строки 1–61) на:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Settings — настройки тенанта/пользователя. 4 рабочие вкладки.
|
||||
*
|
||||
* Источник дизайна: liderra_v8_handoff/concepts/v8_settings.html.
|
||||
* Полностью реализованы (с UI-разводкой): Профиль, Безопасность, API и Webhook,
|
||||
* Уведомления (матрица 8×3 по schema v8.7 §4 users.notification_preferences).
|
||||
*
|
||||
* Аудит D6/D7 (Sprint 3E, 2026-05-16): placeholder-вкладки Проекты/Команда/
|
||||
* Интеграции/Тихие часы убраны — UI не должен обещать «в разработке».
|
||||
* «Проекты» дублировали /projects; «Команда» и «Тихие часы» (ТЗ §17.8)
|
||||
* требуют schema+backend (отдельные эпики); «Интеграции» внешне-блокированы (Б-1).
|
||||
* Вкладки вернутся при реальной реализации соответствующих модулей.
|
||||
*/
|
||||
import { ref } from 'vue';
|
||||
import ApiTab from './settings/ApiTab.vue';
|
||||
import NotificationsTab from './settings/NotificationsTab.vue';
|
||||
import ProfileTab from './settings/ProfileTab.vue';
|
||||
import SecurityTab from './settings/SecurityTab.vue';
|
||||
|
||||
interface Tab {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
const tabs: Tab[] = [
|
||||
{ id: 'profile', label: 'Профиль', icon: 'mdi-account-outline' },
|
||||
{ id: 'security', label: 'Безопасность', icon: 'mdi-shield-lock-outline' },
|
||||
{ id: 'api', label: 'API и Webhook', icon: 'mdi-api' },
|
||||
{ id: 'notifications', label: 'Уведомления', icon: 'mdi-bell-outline' },
|
||||
];
|
||||
|
||||
const activeTab = ref('profile');
|
||||
</script>
|
||||
```
|
||||
|
||||
В `<template>` заменить блок `<v-card variant="outlined" class="tab-pane pa-6">…</v-card>` (строки 89–99) на:
|
||||
|
||||
```vue
|
||||
<v-card variant="outlined" class="tab-pane pa-6">
|
||||
<ProfileTab v-if="activeTab === 'profile'" />
|
||||
<SecurityTab v-else-if="activeTab === 'security'" />
|
||||
<ApiTab v-else-if="activeTab === 'api'" />
|
||||
<NotificationsTab v-else-if="activeTab === 'notifications'" />
|
||||
</v-card>
|
||||
```
|
||||
|
||||
`<style scoped>` — без изменений. Удаляются: импорт `PlaceholderTab`, импорт `computed` (становится неиспользуемым — остаётся только `ref`), `placeholderProps` computed, 4 строки placeholder-вкладок в `tabs`, `<PlaceholderTab>` в шаблоне.
|
||||
|
||||
- [ ] **Step 4: Удалить `PlaceholderTab.vue`**
|
||||
|
||||
Удалить файл `app/resources/js/views/settings/PlaceholderTab.vue` (`git rm`). Компонент больше нигде не импортируется (grep `PlaceholderTab` по `app/resources/js` → только `SettingsView.vue`, который мы уже почистили).
|
||||
|
||||
- [ ] **Step 5: Прогнать тест — убедиться, что зелёный**
|
||||
|
||||
Run: `cd app && npm run test:vue -- --run SettingsView`
|
||||
Expected: PASS — все 8 тестов SettingsView зелёные.
|
||||
|
||||
- [ ] **Step 6: Проверить vue-tsc и ESLint**
|
||||
|
||||
Run: `cd app && npm run type-check` → 0 ошибок (важно: неиспользуемый импорт `computed` удалён, иначе vue-tsc/ESLint ругнётся).
|
||||
Run: `cd app && npm run lint:vue` → 0 ошибок.
|
||||
|
||||
- [ ] **Step 7: Полный прогон Vitest (регрессия)**
|
||||
|
||||
Run: `cd app && npm run test:vue`
|
||||
Expected: 0 failed. Базовый объём перед изменением — 100 файлов / 838 passed / 3 skipped; после Sprint 3E удалён 1 тест → ожидается 100 файлов / 837 passed / 3 skipped (точное число — из реального вывода, не экстраполировать).
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/views/SettingsView.vue app/tests/Frontend/SettingsView.spec.ts
|
||||
git rm app/resources/js/views/settings/PlaceholderTab.vue
|
||||
git commit -m "feat(settings): D6/D7 — убрать placeholder-вкладки SettingsView"
|
||||
```
|
||||
|
||||
**НЕ стейджить** `app/dev-indices.json` (авто-генерируемый, pre-existing `M`).
|
||||
|
||||
---
|
||||
|
||||
## Self-Review
|
||||
|
||||
- Spec coverage: D6 (4 placeholder-вкладки убраны) ✅; D7 (left-rail 8→4) ✅. «Импорт»-вкладка из D7 — H8/Sprint 4, явно вне scope.
|
||||
- Placeholder scan: нет TODO/TBD; весь код приведён дословно.
|
||||
- Type consistency: `tabs` остаётся `Tab[]`; `activeTab` — `ref('profile')`; `computed` удалён вместе с единственным потребителем `placeholderProps`.
|
||||
@@ -0,0 +1,536 @@
|
||||
# A3 Integration-Tooling Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Наполнить пустой раздел A3 «Программирование — интеграции (API, вебхуки)» карты `automation-graph.html` — формализовать 2 новых интеграционных инструмента + кросс-реф 5 существующих, синхронизировать 4 нормативных файла.
|
||||
|
||||
**Architecture:** Параллельно A6/D3. Новый аддитивный слой `NODE_SECTION_SECONDARY` в карте (`NODE_SECTION` 1:1 не трогается) даёт кросс-реф существующих узлов в A3. `openapi-mcp-server` — реальная установка (`.mcp.json`), `api-docs` agent — 0-install (claude-flow). Нормативка — A6/D3-паттерн (Tooling §4.22 + PSR_v1 R10.1 + Pravila §13.2 + CLAUDE.md через `claude-md-improver`).
|
||||
|
||||
**Tech Stack:** `automation-graph.html` (vis.js, vanilla JS), `.mcp.json` (stdio MCP), Markdown (Tooling/PSR_v1/Pravila/CLAUDE.md), claude-flow `api-docs` agent, npm/npx.
|
||||
|
||||
**Спецификация:** [docs/superpowers/specs/2026-05-17-a3-integration-tooling-design.md](../specs/2026-05-17-a3-integration-tooling-design.md)
|
||||
|
||||
---
|
||||
|
||||
## Verification Approach
|
||||
|
||||
Интеграция — **документация + конфиг + vis.js-карта**, прикладного кода (Laravel/Vue) не трогает → unit-TDD-поверхности нет (как у A6/D3). Верификация по задачам:
|
||||
|
||||
- lefthook pre-commit (gitleaks / markdownlint / cspell / adr-judge) — на каждом коммите;
|
||||
- MCP-smoke — `openapi-mcp-server` поднимается в stdio;
|
||||
- визуальный smoke карты — 118 узлов, 0 JS-ошибок в консоли, панель «Разделы» показывает A3;
|
||||
- регрессия `quick` (lint/format/type-check) — перед финальным коммитом нормативки.
|
||||
|
||||
Если cspell блокирует новый валидный термин — добавить в `cspell-words.txt` (в том же коммите).
|
||||
|
||||
**Изоляция:** работа в worktree `.claude/worktrees/a3-integration-tooling` (ветка `feat/a3-integration-tooling`). В native-worktree `lefthook` может быть не в PATH (квирк #97) → pre-commit хуки молча пропускаются; cspell/gitleaks гонять вручную перед коммитом/push.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Create: `docs/api/openapi.yaml` — стартовый OpenAPI-скелет (smoke, deals API)
|
||||
- Modify: `.mcp.json` — +`openapi` server entry
|
||||
- Modify: `docs/automation-graph.html` — `NODE_SECTION_SECONDARY` слой + 2 узла + рендер
|
||||
- Modify: `docs/Tooling_v8_3.md` — §4.22 + §0 счётчик, v2.8→v2.9
|
||||
- Modify: `docs/Plugin_stack_rules_v1.md` — R10.1 Блок 3, v3.8→v3.9
|
||||
- Modify: `docs/Pravila_raboty_Claude_v1_1.md` — §13.2, v1.22→v1.23
|
||||
- Modify: `CLAUDE.md` — через `/claude-md-management:claude-md-improver`, v2.8→v2.9
|
||||
- Modify: `cspell-words.txt` — новые термины по мере надобности
|
||||
- Modify: `memory/project_automation_map.md`, `memory/reference_archive.md` — после push
|
||||
|
||||
---
|
||||
|
||||
## Task 1: OpenAPI-скелет через api-docs agent (smoke)
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `docs/api/openapi.yaml`
|
||||
- Read (контекст для агента): `app/routes/api.php`, `app/app/Http/Controllers/` (deals-контроллеры)
|
||||
|
||||
- [ ] **Step 1: Найти роуты deals API**
|
||||
|
||||
Run: `Grep` по `app/routes/api.php` паттерн `deals` (output_mode content). Зафиксировать список эндпоинтов `/api/deals*` (index/show/store/update/transition/destroy/restore/export).
|
||||
|
||||
- [ ] **Step 2: Dispatch api-docs agent**
|
||||
|
||||
Через `Agent` tool, `subagent_type: api-docs`, model `sonnet` (механическая генерация). Промпт: «Сгенерируй OpenAPI 3.1 скелет ТОЛЬКО для группы эндпоинтов `/api/deals*` проекта Лидерра (Laravel 13). Источник — `app/routes/api.php` + контроллеры. Только paths + базовые request/response shapes, без полной схемы компонентов. Результат — валидный YAML, верни raw-текст файла.»
|
||||
|
||||
- [ ] **Step 3: Записать результат**
|
||||
|
||||
Записать вывод агента в `docs/api/openapi.yaml`. В шапку добавить комментарий:
|
||||
|
||||
```yaml
|
||||
# Стартовый OpenAPI-скелет (smoke A3-интеграции, 2026-05-17).
|
||||
# Покрывает только группу /api/deals*. Полная спека REST API — отдельная задача вне scope A3.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Валидировать YAML**
|
||||
|
||||
Run: `npx --yes @redocly/cli@latest lint docs/api/openapi.yaml` (или `npx --yes @stoplight/spectral-cli lint`).
|
||||
Expected: парсится без fatal-ошибок (warning'и о неполноте допустимы — это скелет).
|
||||
Если CLI недоступен — минимум: `node -e "require('js-yaml').load(require('fs').readFileSync('docs/api/openapi.yaml','utf8'))"` → без исключения.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/api/openapi.yaml
|
||||
git commit -m "docs(a3): OpenAPI skeleton for /api/deals — A3 smoke artifact"
|
||||
```
|
||||
|
||||
Если cspell блокирует — добавить термины (`openapi`, `redocly`, `spectral` и т.п.) в `cspell-words.txt`, `git add` его, повторить коммит.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Установить и сконфигурировать openapi-mcp-server
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `.mcp.json` (корень репозитория)
|
||||
|
||||
- [ ] **Step 1: Определить точное имя npm-пакета**
|
||||
|
||||
Run: `npm view @ivotoby/openapi-mcp-server version` — основной кандидат (GitHub `ivo-toby/mcp-openapi-server`).
|
||||
Fallback при ошибке: `npm view openapi-mcp-server version`, затем `npm view mcp-openapi-server version`.
|
||||
Зафиксировать имя пакета с непустой версией и репозиторием `ivo-toby/mcp-openapi-server`. Обозначить как `<PKG>`.
|
||||
|
||||
- [ ] **Step 2: Прочитать текущий `.mcp.json`**
|
||||
|
||||
Run: `Read` по `.mcp.json`. Зафиксировать формат существующих stdio-серверов (`redis`, `sentry`) — `command` / `args` / `env`.
|
||||
|
||||
- [ ] **Step 3: Добавить server-блок `openapi`**
|
||||
|
||||
В объект `mcpServers` добавить рядом с `redis`/`sentry` (stdio, через `npx`, без глобальной установки — как redis MCP):
|
||||
|
||||
```json
|
||||
"openapi": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "<PKG>"],
|
||||
"env": {
|
||||
"API_BASE_URL": "http://localhost",
|
||||
"OPENAPI_SPEC_PATH": "./docs/api/openapi.yaml"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Точные имена env-переменных свериться с README пакета (Step 1 дал репозиторий) — `API_BASE_URL` / `OPENAPI_SPEC_PATH` либо CLI-флаги `--api-base-url` / `--openapi-spec`. Использовать тот вариант, что в README пакета.
|
||||
|
||||
- [ ] **Step 4: Smoke — сервер поднимается**
|
||||
|
||||
Run: `npx -y <PKG> --help` (проверка, что пакет ставится и запускается на native-Windows).
|
||||
Expected: печатает usage без краша. Если падает (native-Windows несовместимость / кириллица в пути, квирк #26) — **fallback:** оставить server-блок в `.mcp.json` закомментированным-эквивалентом не выйдет (JSON не поддерживает комментарии) → задокументировать в Tooling §4.22 статус «pending: native-Windows install не верифицирован» (прецедент — Sentry MCP «pending Б-1»), узел карты остаётся. Зафиксировать факт в коммите Step 5.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add .mcp.json
|
||||
git commit -m "feat(a3): register openapi-mcp-server in .mcp.json"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Карта — слой NODE_SECTION_SECONDARY + интеграция в рендер
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `docs/automation-graph.html` (3 точки: после `NODE_SECTION`, build `SECTION_NODES`, `ld-section` в Паспорте, `showSectionsLegend`)
|
||||
|
||||
- [ ] **Step 1: Добавить объект `NODE_SECTION_SECONDARY`**
|
||||
|
||||
После закрывающей `};` объекта `NODE_SECTION` (локализовать Grep'ом: закрывающая `};` объекта `NODE_SECTION` перед `const SECTION_BY_ID`) вставить:
|
||||
|
||||
```js
|
||||
// Вторичная классификация: узел первично в NODE_SECTION, дополнительно — в этих
|
||||
// разделах (кросс-реф). Введено A3-интеграцией 17.05.2026 — раздел A3 наполняется
|
||||
// частично кросс-реф существующих интеграционных инструментов. NODE_SECTION 1:1 не трогается.
|
||||
const NODE_SECTION_SECONDARY = {
|
||||
mcp_boost: ['A3'],
|
||||
context7: ['A3'],
|
||||
ag_pest: ['A3'],
|
||||
mcp_semgrep: ['A3'],
|
||||
mcp_sentry: ['A3'],
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Модифицировать build `SECTION_NODES`**
|
||||
|
||||
Текущий код (локализовать Grep'ом `const SECTION_NODES = new Map`):
|
||||
|
||||
```js
|
||||
const SECTION_NODES = new Map(SECTIONS.map(s => [s.id, []]));
|
||||
NODES.forEach(n => {
|
||||
const sid = NODE_SECTION[n.id];
|
||||
if (sid && SECTION_NODES.has(sid)) SECTION_NODES.get(sid).push(n.id);
|
||||
});
|
||||
```
|
||||
|
||||
Заменить на:
|
||||
|
||||
```js
|
||||
const SECTION_NODES = new Map(SECTIONS.map(s => [s.id, []]));
|
||||
NODES.forEach(n => {
|
||||
const sid = NODE_SECTION[n.id];
|
||||
if (sid && SECTION_NODES.has(sid)) SECTION_NODES.get(sid).push(n.id);
|
||||
(NODE_SECTION_SECONDARY[n.id] || []).forEach(secId => {
|
||||
if (SECTION_NODES.has(secId)) SECTION_NODES.get(secId).push(n.id);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Модифицировать строку «Раздел» Паспорта**
|
||||
|
||||
Текущий код (локализовать Grep'ом `ld-section` в `showNodeLegend`):
|
||||
|
||||
```js
|
||||
const _sec = NODE_SECTION[nodeId] ? SECTION_BY_ID.get(NODE_SECTION[nodeId]) : null;
|
||||
document.getElementById('ld-section').textContent = _sec ? `${_sec.id} · ${_sec.label}` : '—';
|
||||
```
|
||||
|
||||
Заменить на:
|
||||
|
||||
```js
|
||||
const _sec = NODE_SECTION[nodeId] ? SECTION_BY_ID.get(NODE_SECTION[nodeId]) : null;
|
||||
const _secExtra = (NODE_SECTION_SECONDARY[nodeId] || [])
|
||||
.map(id => SECTION_BY_ID.get(id)).filter(Boolean);
|
||||
let _secText = _sec ? `${_sec.id} · ${_sec.label}` : '—';
|
||||
if (_secExtra.length) _secText += ` (+${_secExtra.map(s => s.id).join(', ')})`;
|
||||
document.getElementById('ld-section').textContent = _secText;
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Проверить, что счётчик в `showSectionsLegend` уже корректен**
|
||||
|
||||
(Локализовать Grep'ом `nodeIds.length` в `showSectionsLegend`): `nodeIds.length` берётся из `SECTION_NODES.get(sec.id)` — после Step 2 счётчик автоматически учитывает кросс-реф. Правок не требуется. Зафиксировать факт (no-op проверка).
|
||||
|
||||
- [ ] **Step 5: Визуальный smoke (промежуточный)**
|
||||
|
||||
Открыть `docs/automation-graph.html` через Playwright MCP (`browser_navigate` file:// — квирк #90: file:// отвергается → использовать локальный сервер `npx -y serve docs -l 8123` + `browser_navigate http://localhost:8123/automation-graph.html`). Нажать «📂 Разделы». Раздел A3 пока показывает 5 узлов (context7/Boost/Pest/Semgrep/Sentry) — новых узлов ещё нет. Консоль (`browser_console_messages`) — 0 ошибок.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/automation-graph.html
|
||||
git commit -m "feat(map): NODE_SECTION_SECONDARY layer — cross-ref nodes into A3"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Карта — 2 новых узла A3
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `docs/automation-graph.html` (5 точек: `NODES`, `NODE_SECTION`, `NODE_DETAILS` блок `nd()`, `NODE_TIMELINE`, `EDGES`, комментарий-счётчик)
|
||||
|
||||
- [ ] **Step 1: Добавить узел `ag_apidocs` в `NODES`**
|
||||
|
||||
В секции агентов (после `ag_rls`, формат-образец `ag_pest` — Grep `id: 'ag_pest'` — `group: 'agents'`, `ring: 4`) вставить:
|
||||
|
||||
```js
|
||||
// A3 integration-tooling (17.05.2026) — agent раздела «Программирование — интеграции»
|
||||
{ id: 'ag_apidocs', label: 'api-docs (agent)', group: 'agents', size: 18, ring: 4, ...pos(4, 85) },
|
||||
```
|
||||
|
||||
`pos(4, 85)` — свободный угол между `ag_guide` (`pos(4,70)`) и `hk_session` (`pos(4,100)`); при перекрытии на визуальном smoke (Step 6 Task 5) сдвинуть на ±5.
|
||||
|
||||
- [ ] **Step 2: Добавить узел `mcp_openapi` в `NODES`**
|
||||
|
||||
В секции MCP-серверов (после `mcp_semgrep`, формат-образец `mcp_boost` — Grep `id: 'mcp_boost'` — `group: 'mcp'`, `ring: 5`) вставить:
|
||||
|
||||
```js
|
||||
// A3 integration-tooling (17.05.2026) — MCP-сервер раздела «Программирование — интеграции»
|
||||
{ id: 'mcp_openapi', label: 'MCP: openapi', group: 'mcp', size: 20, ring: 5, ...pos(5, 330) },
|
||||
```
|
||||
|
||||
`pos(5, 330)` — свободный угол после `mcp_redis` (`pos(5,310)`); при перекрытии сдвинуть.
|
||||
|
||||
- [ ] **Step 2a: Свериться с фактическим положением узлов MCP/agents**
|
||||
|
||||
Run: `Grep` по `automation-graph.html` паттерн `id: 'mcp_semgrep'|id: 'ag_rls'` — подтвердить точку вставки и отсутствие конфликта углов `pos()` с соседями.
|
||||
|
||||
- [ ] **Step 3: Добавить записи в `NODE_SECTION`**
|
||||
|
||||
В объекте `NODE_SECTION` после строки с A6-узлами (`adr_kit: 'A6', arch_patterns: 'A6', mermaid_skill: 'A6',`) или после D3-узлов — добавить:
|
||||
|
||||
```js
|
||||
// A3 integration-tooling 17.05.2026 — раздел «Программирование — интеграции» наполнен
|
||||
ag_apidocs: 'A3', mcp_openapi: 'A3',
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Добавить `nd()`-детали в `NODE_DETAILS`**
|
||||
|
||||
После D3-блока `nd()` (`tob_skills`/`sec_guidance`, локализовать Grep'ом `tob_skills`) добавить (формат-образец `adr_kit` — Grep `adr_kit: nd\(` — 6 аргументов `nd()`: что делает / когда / ограничения / кому подчиняется / кто подчиняется / с кем работает):
|
||||
|
||||
```js
|
||||
// ── A3 INTEGRATION-TOOLING (17.05.2026) ──────────
|
||||
ag_apidocs: nd(
|
||||
'Агент claude-flow — генерирует OpenAPI-спеку REST API по роутам и контроллерам Laravel. Pattern learning. 0 установки — агент доступен в сессии.',
|
||||
'При фиксации контракта REST API: генерация/обновление OpenAPI-спеки группы эндпоинтов. Результат — docs/api/.',
|
||||
'Sub-агент claude-flow — узел карты, но без отдельного номера в реестре Tooling Прил. Н (реестр — plugin-grain; 11 agent-узлов карты так же без Tooling-номеров). Не UI → вне фильтров R6.0/R6.1/R14.',
|
||||
[{ name: 'CLAUDE.md', cond: '§3.3 — упомянут при #47 openapi-mcp' }],
|
||||
[],
|
||||
[{ name: 'MCP: openapi', cond: 'генерирует спеку → openapi-mcp отдаёт её как MCP-ресурс' }]
|
||||
),
|
||||
mcp_openapi: nd(
|
||||
'MCP-сервер (npm, stdio) — отдаёт OpenAPI-спеку как MCP-ресурс/тулы; introspection своей и чужих API при интеграционной разработке.',
|
||||
'При работе с интеграциями (API/вебхуки) — обращение к структуре OpenAPI-спеки из сессии Claude. READ-ONLY introspection.',
|
||||
'Правило PSR_v1 R10.1 блок 3 (integration-tooling, off-phase — 9-я подкатегория). stdio-режим, без port-conflict. Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.22 #47, CLAUDE.md §3.3 #47.',
|
||||
[{ name: 'PSR_v1', cond: 'R10.1 блок 3: integration-tooling' }, { name: 'Tooling', cond: '§4.22 #47 — реестр' }],
|
||||
[],
|
||||
[{ name: 'docs/api/', cond: 'источник OpenAPI-спеки' }]
|
||||
),
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Добавить записи в `NODE_TIMELINE`**
|
||||
|
||||
После A6/D3-записей в `NODE_TIMELINE` (формат-образец `skill_creator` — Grep `skill_creator:.*since` — `{ since, changed, uses, usesSrc }`) добавить:
|
||||
|
||||
```js
|
||||
// ── A3 INTEGRATION-TOOLING (17.05.2026) ──
|
||||
ag_apidocs: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
mcp_openapi: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Добавить рёбра в `EDGES`**
|
||||
|
||||
После D3-блока рёбер (локализовать Grep'ом `E('psr_v1', 'sec_guidance'`) добавить:
|
||||
|
||||
```js
|
||||
// ── A3 INTEGRATION-TOOLING 17.05.2026 — связи новых узлов ──
|
||||
E('psr_v1', 'mcp_openapi', 'R10.1 блок 3:\nintegration-tooling'),
|
||||
E('tooling', 'mcp_openapi', '§4.22 #47 — реестр'),
|
||||
E('ag_apidocs', 'mcp_openapi', 'спека → MCP-ресурс'),
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Обновить комментарий-счётчик `NODE_SECTION`**
|
||||
|
||||
(Grep-комментарий `Покрывает все NNN узлов карты` — сейчас 116, ставим 118): `// Узел -> раздел. Покрывает все 116 узлов карты.` → `118 узлов`.
|
||||
Run: `Grep` по `automation-graph.html` паттерн `\b(116|117|118)\b.*узлов` и `рёбер` — найти прочие счётчики/метрики, если есть, инкрементировать (узлы +2, рёбра +3). Если иных счётчиков нет — зафиксировать факт.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/automation-graph.html
|
||||
git commit -m "feat(map): A3 nodes — api-docs agent + openapi MCP, section «Программирование — интеграции»"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Визуальный smoke карты
|
||||
|
||||
**Files:** нет правок — только проверка.
|
||||
|
||||
- [ ] **Step 1: Поднять локальный сервер и открыть карту**
|
||||
|
||||
Run (background): `npx -y serve docs -l 8123`. Через Playwright MCP: `browser_navigate http://localhost:8123/automation-graph.html`.
|
||||
|
||||
- [ ] **Step 2: Проверить граф**
|
||||
|
||||
`browser_console_messages` — 0 ошибок JS. Граф рендерится, 2 новых узла (`api-docs (agent)`, `MCP: openapi`) видны в секторах agents/mcp, без перекрытий. При перекрытии — вернуться в Task 4 Step 1/2, скорректировать угол `pos()`.
|
||||
|
||||
- [ ] **Step 3: Проверить панель «Разделы»**
|
||||
|
||||
Нажать «📂 Разделы». Раздел **A3** не пустой, показывает **7 узлов** (api-docs, openapi + 5 кросс-реф). Клик по узлу `MCP: openapi` → Паспорт, строка «Раздел» = `A3 · Программирование — интеграции (API, вебхуки)`. Клик по `MCP: laravel-boost` → строка «Раздел» = `A1 · … (+A3)`.
|
||||
|
||||
- [ ] **Step 4: Скриншот-доказательство**
|
||||
|
||||
`browser_take_screenshot` панели «Разделы» с раскрытым A3 → сохранить как `a3-section-smoke.png` (корень, как `iter-recollage-smoke.png`; **не коммитить** — артефакт smoke).
|
||||
|
||||
- [ ] **Step 5: Остановить сервер**
|
||||
|
||||
Завершить background-процесс `serve`.
|
||||
|
||||
Коммита нет — задача проверочная.
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Tooling Прил. Н — §4.22 + счётчик
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `docs/Tooling_v8_3.md`
|
||||
|
||||
- [ ] **Step 1: Прочитать §4.21/§4.20 (A4-записи)**
|
||||
|
||||
Run: `Read` `docs/Tooling_v8_3.md`, `Grep` паттерн `§4.21|§4.20|§4.19` — зафиксировать структуру/стиль off-phase subsection (#44/#45/#46 design-tooling) для зеркалирования.
|
||||
|
||||
- [ ] **Step 2: Добавить §4.22**
|
||||
|
||||
После §4.21 добавить новый подраздел (зеркалируя стиль §4.19/§4.20/§4.21):
|
||||
|
||||
```markdown
|
||||
### §4.22. #47 openapi-mcp-server — off-phase integration-tooling
|
||||
|
||||
**Пакет:** `<PKG>` (npm, репозиторий `ivo-toby/mcp-openapi-server`), stdio MCP, server `openapi` в `.mcp.json`, tools `mcp__openapi__*`.
|
||||
**Категория:** off-phase, **integration-tooling** (9-я off-phase подкатегория — после UI-пула / infrastructure / debug-runtime / orchestration / architecture-tooling / audit-security / project-management / design-tooling). Раздел A3 карты «Программирование — интеграции (API, вебхуки)».
|
||||
**Назначение:** отдаёт OpenAPI-спеку как MCP-ресурс/тулы; introspection своей и чужих API при интеграционной разработке. READ-ONLY.
|
||||
**Парный узел карты:** `api-docs` agent (claude-flow) — генератор OpenAPI-спеки; узел карты A3 без отдельного Tooling-номера (sub-агент, реестр — plugin-grain).
|
||||
**Координация:** PSR_v1 R10.1 Блок 3 (MCP-серверы). Не UI → не trigger'ит R6.0/R6.1, вне R14 pipeline.
|
||||
**Статус установки:** [подставить из Task 2 Step 4 — «verified» либо «pending: native-Windows install не верифицирован»].
|
||||
```
|
||||
|
||||
`<PKG>` — подставить точное имя из Task 2 Step 1.
|
||||
|
||||
- [ ] **Step 3: Обновить счётчик §0**
|
||||
|
||||
Run: `Grep` по `docs/Tooling_v8_3.md` паттерн `46|формализованных позиций|off-phase` — найти строку-счётчик §0 (после A4 = «46 формализованных позиций: 29 active + 16 off-phase + 1 historic» либо аналог). Инкрементировать: позиций 46→**47**, off-phase 16→**17**. Обновить перечисление off-phase subsections (добавить §4.22) и упоминание подкатегорий (добавить integration-tooling).
|
||||
|
||||
- [ ] **Step 4: Bump версии Прил. Н**
|
||||
|
||||
Шапка/колонтитул Прил. Н: v2.8 → **v2.9**. Добавить changelog-строку «v2.9 от 17.05.2026 — A3 integration-tooling: §4.22 #47 openapi-mcp-server, 9-я off-phase подкатегория integration-tooling; §0 счётчик 46→47. Связано: PSR_v1 v3.9, Pravila v1.23, CLAUDE.md v2.9.»
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/Tooling_v8_3.md
|
||||
git commit -m "docs(a3): Tooling Прил. Н v2.9 — register #47 openapi-mcp-server (§4.22)"
|
||||
```
|
||||
|
||||
cspell-блок → добавить термины в `cspell-words.txt`, повторить.
|
||||
|
||||
---
|
||||
|
||||
## Task 7: PSR_v1 — R10.1 Блок 3
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `docs/Plugin_stack_rules_v1.md`
|
||||
|
||||
- [ ] **Step 1: Прочитать R10.1 Блок 3**
|
||||
|
||||
Run: `Grep` `docs/Plugin_stack_rules_v1.md` паттерн `R10.1|Блок 3|sentry|redis` — зафиксировать структуру Блока 3 (MCP-серверы; sentry/redis с категорией debug-runtime).
|
||||
|
||||
- [ ] **Step 2: Добавить строку в Блок 3**
|
||||
|
||||
В таблицу/список Блока 3 R10.1 добавить строку (зеркалируя строки sentry/redis):
|
||||
|
||||
```markdown
|
||||
| openapi-mcp-server | integration-tooling | off-phase. stdio MCP, server `openapi` в `.mcp.json`. Раздел A3 карты. Не trigger'ит R6.0/R6.1, вне R14 pipeline. Tooling §4.22 #47. |
|
||||
```
|
||||
|
||||
Точный формат строки — по факту таблицы (колонки сверить в Step 1).
|
||||
|
||||
- [ ] **Step 3: Bump версии**
|
||||
|
||||
Шапка PSR_v1: v3.8 → **v3.9**. Changelog-строка «v3.9 от 17.05.2026 — R10.1 Блок 3 +1 строка openapi-mcp-server (категория integration-tooling, off-phase, раздел A3). Не UI → вне R6/R14. Связано: Tooling v2.9, Pravila v1.23, CLAUDE.md v2.9.» Обновить cross-refs шапки, если они перечисляют версии родственных файлов.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/Plugin_stack_rules_v1.md
|
||||
git commit -m "docs(a3): PSR_v1 v3.9 — R10.1 Блок 3 +openapi-mcp (integration-tooling)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Pravila — §13.2
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `docs/Pravila_raboty_Claude_v1_1.md`
|
||||
|
||||
- [ ] **Step 1: Прочитать §13.2**
|
||||
|
||||
Run: `Grep` `docs/Pravila_raboty_Claude_v1_1.md` паттерн `§13.2|Off-phase|audit-security|architecture-tooling` — зафиксировать структуру абзацев off-phase подкатегорий (последний добавленный — audit-security, D3).
|
||||
|
||||
- [ ] **Step 2: Добавить абзац**
|
||||
|
||||
После абзаца «Off-phase audit-security» добавить:
|
||||
|
||||
```markdown
|
||||
**Off-phase integration-tooling.** Инструменты раздела A3 карты «Программирование — интеграции (API, вебхуки)» — #47 `openapi-mcp-server` (Tooling §4.22; введён A3-интеграцией 17.05.2026) и `api-docs` agent (claude-flow, узел карты A3 без отдельного Tooling-номера). Off-phase, не UI → вне R6/R14 PSR_v1. READ-ONLY introspection. Регулируются PSR_v1 R10.1 Блок 3.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Bump версии + счётчик правил**
|
||||
|
||||
Шапка Pravila: v1.22 → **v1.23**. Если §13.2 (или §11.5) содержит счётчик подкатегорий/правил — свериться `Grep`'ом и инкрементировать. Changelog-строка «v1.23 от 17.05.2026 — §13.2 +абзац Off-phase integration-tooling (#47 openapi-mcp-server / api-docs agent — раздел A3 карты). Связано: Tooling v2.9, PSR_v1 v3.9, CLAUDE.md v2.9.»
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/Pravila_raboty_Claude_v1_1.md
|
||||
git commit -m "docs(a3): Pravila v1.23 — §13.2 +Off-phase integration-tooling"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: CLAUDE.md — через claude-md-management
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `CLAUDE.md` (только через скил — §5 п.10)
|
||||
|
||||
- [ ] **Step 1: Инвокировать claude-md-improver**
|
||||
|
||||
Invoke `/claude-md-management:claude-md-improver` со списком targeted-правок:
|
||||
|
||||
- §3 title «Карта 46 инструментов» → «47»;
|
||||
- §3.3 +строка #47 openapi-mcp-server (integration-tooling, off-phase; Tooling §4.22) + упоминание `api-docs` agent как парного узла карты A3 без Tooling-номера;
|
||||
- §1 priority-chain row 2b «реестр 46» → «47»;
|
||||
- §3.3 footer count 46→47 + integration-tooling как 9-я off-phase подкатегория;
|
||||
- §3.4 нумерационная сноска — добавить #47 в перечисление off-phase, обновить арифметику;
|
||||
- §0 cross-refs: Pravila v1.22→**v1.23**, PSR_v1 v3.8→**v3.9**, Tooling v2.8→**v2.9**;
|
||||
- §6 +абзац A3 integration-tooling (по образцу абзацев A6/D3/A4);
|
||||
- шапка v2.8 → **v2.9** + §9 changelog-запись.
|
||||
|
||||
- [ ] **Step 2: Проверить синхронность (§5 п.7)**
|
||||
|
||||
Убедиться, что внутри flow скила Tooling (Task 6) и Pravila (Task 8) уже синхронизированы — версии в §0 cross-refs CLAUDE.md совпадают с фактическими шапками файлов.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
Скил/ручной коммит:
|
||||
|
||||
```bash
|
||||
git add CLAUDE.md
|
||||
git commit -m "docs(a3): CLAUDE.md v2.9 — register #47 openapi-mcp-server (A3 integration-tooling)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 10: Регрессия + память + handoff на push
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `memory/project_automation_map.md`, `memory/reference_archive.md` (после успешной регрессии)
|
||||
|
||||
- [ ] **Step 1: Регрессия quick**
|
||||
|
||||
Invoke skill `regression` с аргументом `quick` (lint/format/type-check). Зафиксировать канонический статус-лайн + вердикт. Ожидание GREEN — правки только в `.md`/`.html`/`.json`/`.yaml`, прикладной код не тронут.
|
||||
|
||||
- [ ] **Step 2: Финальная сверка счётчика (риск нумерации)**
|
||||
|
||||
Run: `git log --oneline origin/main..HEAD` + `Grep` Tooling §0 — подтвердить, что #47/§4.22 не пересёкся с A11, если та смёрджилась в main (C9/deptrac/A4 уже влиты). При коллизии — перенумеровать openapi-mcp на следующий свободный номер во всех 4 файлах + карте, повторить затронутые коммиты.
|
||||
|
||||
- [ ] **Step 3: Обновить memory**
|
||||
|
||||
- `memory/project_automation_map.md` — метрики 116→118 узлов / +3 ребра, раздел A3 наполнен (7 узлов: 2 новых + 5 кросс-реф), `NODE_SECTION_SECONDARY` слой.
|
||||
- `memory/reference_archive.md` — версии Tooling v2.9 / PSR_v1 v3.9 / Pravila v1.23 / CLAUDE.md v2.9.
|
||||
- `MEMORY.md` — обновить строки-указатели при необходимости.
|
||||
|
||||
- [ ] **Step 4: Commit memory**
|
||||
|
||||
```bash
|
||||
git add memory/
|
||||
git commit -m "docs(a3): memory sync — A3 integration-tooling closed"
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Handoff на push**
|
||||
|
||||
Не пушить автоматически. Представить заказчику: ветка `feat/a3-integration-tooling`, перечень коммитов, напоминание про pre-push (`gitleaks` full-history + `lychee`) и про порядок merge относительно D3/A11/C9. Push — по явному «пушь» (паттерн `git push origin feat/a3-integration-tooling:main`).
|
||||
|
||||
---
|
||||
|
||||
## Self-Review
|
||||
|
||||
**Spec coverage:**
|
||||
|
||||
- spec §3.1 (2 новых узла) → Task 1 (api-docs smoke), Task 2 (openapi-mcp install), Task 4 (узлы карты). ✓
|
||||
- spec §3.2 (5 кросс-реф) → Task 3 (`NODE_SECTION_SECONDARY`). ✓
|
||||
- spec §4 (правка модели карты) → Task 3 + Task 4 + Task 5. ✓
|
||||
- spec §5 (нормативка 4 файла) → Task 6 (Tooling), 7 (PSR_v1), 8 (Pravila), 9 (CLAUDE.md). ✓
|
||||
- spec §6 (smoke/верификация) → Task 1 Step 4, Task 2 Step 4, Task 5, Task 10 Step 1. ✓
|
||||
- spec §7 (риск нумерации) → Task 10 Step 2. ✓
|
||||
- spec §8 (ветка/артефакты) → ветка создана (`feat/a3-integration-tooling`), spec/plan на месте, push-handoff Task 10 Step 5. ✓
|
||||
|
||||
**Placeholder scan:** `<PKG>` — намеренный плейсхолдер, разрешается в Task 2 Step 1 (`npm view`) и подставляется в Task 2 Step 3 / Task 6 Step 2; «[подставить из Task 2 Step 4]» — статус установки, разрешается в рамках Task 2. Иных плейсхолдеров нет.
|
||||
|
||||
**Type consistency:** имена узлов `ag_apidocs` / `mcp_openapi` — единообразны во всех задачах (NODES, NODE_SECTION, NODE_DETAILS, NODE_TIMELINE, EDGES, NODE_SECTION_SECONDARY не содержит их — они первичны в A3). Объект `NODE_SECTION_SECONDARY` — одно имя везде. Версии: Tooling v2.9 / PSR_v1 v3.9 / Pravila v1.23 / CLAUDE.md v2.9 — консистентны в Tasks 6-9 и §0 cross-refs.
|
||||
@@ -0,0 +1,509 @@
|
||||
# A4 Design Tooling Integration Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Close the gaps in the `A4 «Дизайн (UI/UX, графика, бренд)»` map section by adding three tools to the current three (FD #30 / UPM #31 / 21st #32) — #41 **Figma MCP** (design source + brand tokens), #42 **Universal Icons MCP** (graphics / icons), #43 **Design plugin** (design review / a11y critique / UX writing) — so A4 grows from 3 → 6 nodes with full UI/UX + графика + бренд coverage.
|
||||
|
||||
**Architecture:** Three tools, three install modes. **Design plugin** = Anthropic-Verified marketplace plugin (review-side, like FD — no code-gen). **Universal Icons MCP** = stdio npm MCP server, MIT, framework-neutral SVG. **Figma MCP** = official remote MCP server (`https://mcp.figma.com/mcp`), used in **extract-only** role. The two pre-identified overlaps (Figma MCP code-gen ↔ FD solver; Design plugin a11y/review ↔ FD/Pa11y/R5) are closed by **ADR-004** boundary decisions + PSR_v1 rows — not left implicit.
|
||||
|
||||
**Tech Stack:** Figma MCP (official, remote HTTP transport, OAuth); `mcp-universal-icons` (npm, MIT, stdio); Design plugin (`anthropics/claude-plugins-official` marketplace, Anthropic Verified); adr-kit v0.13.1 (already installed #36 — used to author ADR-004); project normative docs; `docs/automation-graph.html` (vis.js).
|
||||
|
||||
---
|
||||
|
||||
## Plan Correction (2026-05-17, after Task 1 pre-flight)
|
||||
|
||||
Facts verified on branch `feat/a4-design-tooling` (from `origin/main` `9b63e27`) supersede assumptions in the tasks below:
|
||||
|
||||
- **FM2 resolved — no Figma account.** Figma MCP install (Task 4) is **deferred**, precondition «Figma account + file created». It is still registered (Task 6) as **deferred-pending** (precedent: Sentry MCP #34 «pending Б-1») and appears on the map (Task 7) marked deferred. A4 ships **5 live nodes now**; the 6th (Figma) activates later.
|
||||
- **Registry numbering.** deptrac already took #43 (Tooling §4.18, §0 counter **43**). A4 tools are therefore **#44 Figma MCP / #45 Universal Icons MCP / #46 Design plugin**, Tooling subsections **§4.19 / §4.20 / §4.21**, §0 counter **43 → 46**. Every `#41 / #42 / #43` in the tasks below reads as `#44 / #45 / #46`.
|
||||
- **Plugins / marketplace.** `~/.claude/settings.json` `enabledPlugins` holds **21** plugins (not 9); installing Design plugin → **22**. Marketplace `claude-plugins-official` is **already present** — no `/plugin marketplace add` needed.
|
||||
- **lefthook** has **10** jobs (deptrac = job 10). Task 1 baseline: all 10 green / "no staged files".
|
||||
- **ADR id — ADR-006, not ADR-004.** ADR-004/005 were already taken (project-management / deptrac epics). The A4 boundaries ADR is `docs/adr/ADR-006-a4-design-tooling-boundaries.md`. Every `ADR-004` in the tasks / self-review below reads as `ADR-006`.
|
||||
- **Design plugin marketplace — `knowledge-work-plugins`, not `claude-plugins-official`** (verified post-reload, 2026-05-17, against the marketplace manifest). The `design` plugin lives in `anthropics/knowledge-work-plugins` (same marketplace as #42 product-management); `claude-plugins-official` has only `frontend-design`. The `enabledPlugins` entry is `design@knowledge-work-plugins`. Every `design@claude-plugins-official` / `claude-plugins-official` reference to the **Design plugin** in the tasks below reads accordingly (`claude-plugins-official` for #33 claude-md-management / #40 security-guidance stays correct). The `/plugin install` path (Task 2 Steps 1-3) is unavailable in the VSCode-extension environment — the plugin is enabled by editing `enabledPlugins` directly.
|
||||
|
||||
---
|
||||
|
||||
## Tool Identity (verified 2026-05-17 — re-fact-check in Task 1)
|
||||
|
||||
| # | Tool | Install mode | Source | Hooks? |
|
||||
|---|---|---|---|---|
|
||||
| 41 | **Figma MCP** | Remote MCP server → `.mcp.json` (`http` transport, `https://mcp.figma.com/mcp`) | Figma official; Figma↔Claude Code integration announced Feb 2026 | None (MCP server, no CC lifecycle hooks) |
|
||||
| 42 | **Universal Icons MCP** (`mcp-universal-icons`) | stdio MCP server → `.mcp.json` (`npx -y mcp-universal-icons`) | GitHub `awssat/mcp-universal-icons`, **MIT** | None (MCP server) |
|
||||
| 43 | **Design plugin** | Marketplace plugin → `~/.claude/settings.json` `enabledPlugins` | `anthropics/claude-plugins-official`, **Anthropic Verified** | Verify on install (DP4) |
|
||||
|
||||
Kept from the A4 top-6 (already in the map): #30 Frontend Design, #31 UI UX Pro Max, #32 21st Magic MCP.
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions & Conflict Audit
|
||||
|
||||
Gap analysis behind the tool choice (the "что закрываем"):
|
||||
|
||||
| A4 подзона | Покрыто до | Пробел → закрывает |
|
||||
|---|---|---|
|
||||
| UI/UX (компоненты, экраны, паттерны) | FD #30 + UPM #31 + 21st #32 | — (уже плотно) |
|
||||
| Графика (иконки, SVG) | 21st `logo_search` (только логотипы) | иконки/SVG → **#42 Universal Icons** |
|
||||
| Бренд (палитра, типографика, токены, источник) | handoff-доки Платона (статика) + ручной перенос в `vuetify.ts` | живой источник + извлечение токенов → **#41 Figma MCP** |
|
||||
| Сквозное: design-review / визуальный аудит | Pa11y (только технический a11y) | дизайн-критика, UX-копирайт, дизайн-a11y → **#43 Design plugin** |
|
||||
|
||||
Conflict audit (pattern follows the AK/MK/CC audit used for A6). Verified against the project `.mcp.json`, `~/.claude/settings.json`, project `.claude/settings.json`, `lefthook.yml`, `.gitleaks.toml`.
|
||||
|
||||
| # | Tool | Sev | Conflict | Resolution (locked) |
|
||||
|---|---|---|---|---|
|
||||
| FM1 | Figma MCP | 🔴 | Figma MCP can **generate UI code** (design-to-code) → duplicates FD #30 the UI solver → CLAUDE.md §5 п.6 (no two tools on one task). | **ADR-004 Decision 1 + PSR_v1 R10.1 row:** Figma MCP used **extract-only** (design-data + token reads, e.g. `get_variable_defs`). Its code-gen mode is **never invoked**. FD remains the sole UI solver (PSR_v1 R10.2). |
|
||||
| FM2 | Figma MCP | 🔴 | Figma MCP's value (token extraction from source) needs an **accessible live Figma file**. The handoff is currently static (`liderra_v8_handoff/` = `.md` + 13 HTML). No live file → the tool degrades to "describe screenshots". | **Hard gate in Task 1 Step 5.** If no accessible Figma file exists → STOP, surface to user: defer #41 (A4 set drops to 5 — the "в" variant) or obtain access from Платон. Do not install #41 blind. |
|
||||
| FM3 | Figma MCP | 🟡 | Remote MCP auth — if a static token is required it must not leak into the repo. | Prefer remote OAuth flow (no repo secret). If a token IS required → env-var via PowerShell User scope (same pattern as Sentry `SENTRY_AUTH_TOKEN`), referenced in `.mcp.json`, never inlined. gitleaks pre-push must stay 0. |
|
||||
| FM4 | Figma MCP | 🟡 | Figma MCP default code-gen targets React/Tailwind → wrong stack. | Sidestepped by FM1 (extract-only). The PSR_v1 row still states the R6.0 stack-filter applies if any Figma output is consumed as material. |
|
||||
| FM5 | Figma MCP | 🟡 | MCP server #41 unregistered = PSR_v1 R0.2/R10 violation on use. | Register in 4 normative homes (Task 6). |
|
||||
| FM6 | Figma MCP | 🟢 | Lifecycle-hook collision with the 6 economy + 2 ruflo + skill-discipline hooks. | None — MCP server, zero CC lifecycle hooks. Re-verify Task 4 Step 4. |
|
||||
| UI1 | Universal Icons | 🟢 | Overlap with FD #30. | None — icons are an asset-primitive (material); FD decides *which/where*, the MCP only fetches SVG (PSR_v1 R10.2). |
|
||||
| UI2 | Universal Icons | 🟡 | Slight overlap with 21st `logo_search` (logos). | Boundary in ADR-004 note: Universal Icons = UI **icons** (Lucide-first); 21st `logo_search` = brand **logos**. Both kept; no task-level duplication. |
|
||||
| UI3 | Universal Icons | 🟢 | MIT, stdio, hooks/registration footprint. | None — MCP server, zero hooks. Tailwind injection is optional; default SVG output is framework-neutral (R6.0 satisfied by default — never request `jsx`/Tailwind format). Re-verify Task 3 Step 4. |
|
||||
| UI4 | Universal Icons | 🟡 | MCP server #42 unregistered. | Register in 4 normative homes (Task 6). |
|
||||
| DP1 | Design plugin | 🟡 | "Accessibility Audit WCAG 2.1 AA" is a 3-way overlap: Design plugin audit × FD "a11y-принципы" (Tooling §4.4) × Pa11y (technical a11y SoT, CLAUDE.md §5 п.3). | **ADR-004 Decision 2:** Design plugin a11y = design-level critique, **pre-code**. **Pa11y stays the single source of truth** for technical a11y (PSR_v1 R8 — Pa11y wins). FD keeps a11y-principles during design. Three-tier, no override. |
|
||||
| DP2 | Design plugin | 🟡 | "Design Critique" overlaps PSR_v1 R5 (review-by-aspect — UI/UX aspect → FD) and `superpowers:requesting-code-review`/`receiving-code-review`. | **ADR-004 Decision 3:** Design Critique runs in **R2 phase 1** (research / pre-code planning), not phase-8 review. Phase-8 review stays R5 aspect-split + Superpowers review skills. Design plugin does not replace `requesting-code-review`. |
|
||||
| DP3 | Design plugin | 🟡 | Plugin #43 unregistered. | Register in 4 normative homes (Task 6). |
|
||||
| DP4 | Design plugin | 🟢 | May register CC lifecycle hooks → would touch the economy/ruflo chain. | Verify on install (Task 2 Step 4). If it injects a `hooks` entry → STOP, re-audit like A6 AK5. claude-md-management #33 ships zero hooks; security-guidance #40 ships one PreToolUse hook — Design plugin is unknown until installed. |
|
||||
| DP5 | Design plugin | 🟢 | R6.0 stack-filter applicability. | None — Design plugin is review/planning, produces no code → no R6.0 needed (treated like FD's review side). UX-writing output is Russian microcopy — fine. |
|
||||
| CC1 | all 3 | 🟡 | Bus-factor — Universal Icons is a single-maintainer community repo. | Figma MCP (Figma official) + Design plugin (Anthropic) are low-risk. Universal Icons is MIT and trivially replaceable (`iconify-mcp-server` is a drop-in alternative). Note in Tooling §4.x as a known risk. |
|
||||
|
||||
**Severable scope:** Task 4 (Figma MCP) is gated on the FM2 spike. If the spike fails, **skip Task 4 entirely** — Tasks 1-3, 5-8 still deliver A4 at 5 nodes (Design plugin + Universal Icons added). Primary path below = full 6-node integration.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Created / Modified | Responsibility |
|
||||
|---|---|---|
|
||||
| `.mcp.json` | Modify | += `figma` server (Task 4) + `universal-icons` server (Task 3) |
|
||||
| `~/.claude/settings.json` | Modify | `enabledPlugins` += `design@claude-plugins-official`; `extraKnownMarketplaces` unchanged (Anthropic marketplace already present from #33/#40) |
|
||||
| `docs/adr/ADR-004-a4-design-tooling-boundaries.md` | Create | The 2 boundary decisions (FM1 / DP1 / DP2) — authored via adr-kit, no Enforcement block |
|
||||
| `docs/Tooling_v8_3.md` | Modify | Прил. Н — new §4.16/§4.17/§4.18 + §0 counter `40 → 43` |
|
||||
| `docs/Plugin_stack_rules_v1.md` | Modify | R10.1 — 3 new rows; R6/R10/R14 boundary notes for Figma MCP + Universal Icons |
|
||||
| `docs/Pravila_raboty_Claude_v1_1.md` | Modify | §13.2 — design-tooling note |
|
||||
| `CLAUDE.md` | Modify (**via claude-md-management only**) | §3 title count, §1 row 2b count, §3.3 rows #41/#42/#43, §6 integration paragraph |
|
||||
| `docs/CHANGELOG_claude_md.md` | Modify | CLAUDE.md version-bump entry |
|
||||
| `docs/automation-graph.html` | Modify | 3 new nodes (`mcp_figma`, `mcp_icons`, `design_plugin`) → `NODE_SECTION` A4 |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Pre-flight — branch, baseline, fact-check, FM2 spike
|
||||
|
||||
**Files:** none modified (read-only) except branch creation
|
||||
|
||||
- [ ] **Step 1: Resolve working-tree state and create the branch**
|
||||
|
||||
```bash
|
||||
cd "c:/моя/проекты/портал crm/Документация"
|
||||
git status --short
|
||||
git branch --show-current
|
||||
git rev-parse --short HEAD
|
||||
```
|
||||
|
||||
If `CLAUDE.md` or other files are modified from prior D3 work — confirm with the user whether to commit/stash before branching. With a clean tree:
|
||||
|
||||
```bash
|
||||
git checkout -b feat/a4-design-tooling
|
||||
```
|
||||
|
||||
Expected: on `feat/a4-design-tooling`; record HEAD SHA as the regression baseline.
|
||||
|
||||
- [ ] **Step 2: Baseline the pre-commit chain**
|
||||
|
||||
```bash
|
||||
npx lefthook run pre-commit
|
||||
```
|
||||
|
||||
Expected: all 9 jobs (gitleaks, markdownlint, cspell, stylelint, pint, larastan, squawk, eslint-vue, adr-judge) green / "no staged files". Record the count.
|
||||
|
||||
- [ ] **Step 3: Snapshot MCP + plugin state**
|
||||
|
||||
```bash
|
||||
node -e "const c=require('./.mcp.json');console.log(Object.keys(c.mcpServers||c.servers||c))"
|
||||
```
|
||||
|
||||
Read `~/.claude/settings.json` `enabledPlugins` — record the current plugin count (expected 11 after A6). Read the `hooks` block of both `~/.claude/settings.json` and project `.claude/settings.json` — record as the DP4 baseline.
|
||||
|
||||
- [ ] **Step 4: Fact-check the 3 tools**
|
||||
|
||||
Confirm assumptions still hold:
|
||||
|
||||
- Figma MCP — remote endpoint `https://mcp.figma.com/mcp`, `http` transport, OAuth. Confirm the current Claude Code `claude mcp add --transport http` syntax and whether a token is needed (FM3). Source: `help.figma.com` "Claude Code and Figma: Set up the MCP server".
|
||||
- `https://github.com/awssat/mcp-universal-icons` — npm `mcp-universal-icons`, MIT, tools `search_icons`/`get_icon`/`health_check`, Lucide in the collection set, no CC hooks.
|
||||
- `https://claude.com/plugins/design` — confirm the exact marketplace id and plugin name (expected `anthropics/claude-plugins-official`, plugin `design`), Anthropic Verified, and whether it ships hooks.
|
||||
|
||||
If any tool now registers CC lifecycle hooks → note it; DP4/FM6/UI3 re-audit in the install task.
|
||||
|
||||
- [ ] **Step 5: FM2 spike — confirm an accessible live Figma file exists (HARD GATE)**
|
||||
|
||||
```bash
|
||||
grep -ri "figma.com" liderra_v8_handoff/ docs/ 2>/dev/null
|
||||
```
|
||||
|
||||
Then ask the user directly: **does Платон's v8 Forest design exist as a Figma file this project can open (view/inspect access)?**
|
||||
|
||||
- **Accessible Figma file confirmed** → proceed to Task 4 (full 6-node path).
|
||||
- **No accessible Figma file** → STOP. Surface to the user: Figma MCP (#41) loses its core value; either (a) defer #41, ship A4 at 5 nodes (Design plugin + Universal Icons), or (b) obtain Figma access first. Do not proceed to Task 4 until the user decides.
|
||||
|
||||
No repo files changed in Task 1 → no commit (branch creation aside).
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Install the Design plugin (#43, marketplace)
|
||||
|
||||
**Files:** Modify `~/.claude/settings.json` (the `Edit` triggers the `ask` permission — expected)
|
||||
|
||||
- [ ] **Step 1: Add the marketplace if absent**
|
||||
|
||||
The Anthropic marketplace is already present (used by #33 claude-md-management and #40 security-guidance). If `extraKnownMarketplaces` lacks it:
|
||||
|
||||
```
|
||||
/plugin marketplace add anthropics/claude-plugins-official
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Install the plugin**
|
||||
|
||||
```
|
||||
/plugin install design@claude-plugins-official
|
||||
```
|
||||
|
||||
(Use the exact plugin id confirmed in Task 1 Step 4.)
|
||||
|
||||
- [ ] **Step 3: Reload**
|
||||
|
||||
```
|
||||
/reload-plugins
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify `enabledPlugins` and the hook baseline (DP4)**
|
||||
|
||||
Read `~/.claude/settings.json`. `enabledPlugins` must now contain `design@claude-plugins-official: true` → **12 plugins total** (was 11). Read the `hooks` block of `~/.claude/settings.json` AND project `.claude/settings.json` — both must be **unchanged** vs the Task 1 Step 3 baseline. If the Design plugin injected a `hooks` entry → **STOP**, re-audit DP4 (the economy/ruflo chain must not be perturbed).
|
||||
|
||||
- [ ] **Step 5: Smoke-test the plugin**
|
||||
|
||||
Invoke a Design-plugin capability — e.g. ask for a design critique of one existing screen description, or a UX-writing pass on a short microcopy string. Confirm the plugin's skill activates and returns design-review output (not code).
|
||||
|
||||
- [ ] **Step 6: Confirm economy/ruflo chain intact**
|
||||
|
||||
Submit a trivial prompt; confirm the economy marker still appears, no hook errors. No repo files changed in Task 2 → no commit.
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Install Universal Icons MCP (#42, stdio)
|
||||
|
||||
**Files:** Modify `.mcp.json`
|
||||
|
||||
- [ ] **Step 1: Add the MCP server**
|
||||
|
||||
```bash
|
||||
claude mcp add universal-icons -- npx -y mcp-universal-icons
|
||||
```
|
||||
|
||||
If `claude mcp add` writes to the user scope instead of the project `.mcp.json`, add the block manually to project `.mcp.json` alongside the existing servers:
|
||||
|
||||
```json
|
||||
"universal-icons": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mcp-universal-icons"]
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Reload and verify the server connects**
|
||||
|
||||
```
|
||||
/reload-plugins
|
||||
```
|
||||
|
||||
Confirm a `universal-icons` MCP server appears and its tools (`mcp__universal-icons__search_icons`, `mcp__universal-icons__get_icon`, `mcp__universal-icons__health_check`) are listed.
|
||||
|
||||
- [ ] **Step 3: Smoke-test — Lucide icon search**
|
||||
|
||||
Call `search_icons` for an icon known to exist in Лидерра's set (e.g. `bell`, `chevron-down`) restricted to the Lucide collection, then `get_icon` for one result. Confirm: a result is returned, the SVG is **framework-neutral** (no Tailwind classes, no JSX) — default `svg` format. Never request `jsx`/Tailwind output (UI3 / R6.0).
|
||||
|
||||
- [ ] **Step 4: Verify no hooks / no settings drift (UI3)**
|
||||
|
||||
Read the `hooks` block of `~/.claude/settings.json` and project `.claude/settings.json` — unchanged vs Task 1 baseline. `enabledPlugins` unchanged (MCP server ≠ plugin).
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add .mcp.json
|
||||
git commit -m "feat(a4): add Universal Icons MCP server (#42, design graphics)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Install Figma MCP (#41, remote — gated on Task 1 Step 5)
|
||||
|
||||
**Files:** Modify `.mcp.json`
|
||||
|
||||
> Skip this entire task if the FM2 spike (Task 1 Step 5) failed and the user chose to defer #41.
|
||||
|
||||
- [ ] **Step 1: Add the remote MCP server**
|
||||
|
||||
Using the syntax confirmed in Task 1 Step 4:
|
||||
|
||||
```bash
|
||||
claude mcp add --transport http figma https://mcp.figma.com/mcp
|
||||
```
|
||||
|
||||
Or manually in project `.mcp.json`:
|
||||
|
||||
```json
|
||||
"figma": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.figma.com/mcp"
|
||||
}
|
||||
```
|
||||
|
||||
If Task 1 found a static token is required (FM3): set it as a PowerShell User-scope env var (e.g. `FIGMA_MCP_TOKEN`) and reference it via env in `.mcp.json` — **never inline the token**.
|
||||
|
||||
- [ ] **Step 2: Authenticate**
|
||||
|
||||
Complete the Figma OAuth flow (browser) when prompted on first server use. Confirm the project's Figma account has at least view/inspect access to the v8 Forest file confirmed in Task 1 Step 5.
|
||||
|
||||
- [ ] **Step 3: Reload and verify the server connects**
|
||||
|
||||
```
|
||||
/reload-plugins
|
||||
```
|
||||
|
||||
Confirm the `figma` MCP server appears and its tools are listed (expect a `get_variable_defs`-class tool and design-data read tools).
|
||||
|
||||
- [ ] **Step 4: Smoke-test — extract-only (FM1)**
|
||||
|
||||
Point Figma MCP at a node in the Forest file and call the variable/token-extraction tool (`get_variable_defs` or equivalent). Confirm color/spacing/typography variables are returned. **Do NOT invoke any code-generation tool** — extract-only is the locked role (FM1). Verify no `hooks` drift in either `settings.json` (FM6).
|
||||
|
||||
- [ ] **Step 5: Cross-check one token against the source of truth**
|
||||
|
||||
Compare one extracted color against `app/resources/js/plugins/vuetify.ts` (Teal `#0F6E56` / ivory `#F6F3EC`). They should agree — this proves the Figma file is the genuine Forest source and the extraction is usable. Note any drift for the user; do not "fix" `vuetify.ts` in this plan.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add .mcp.json
|
||||
git commit -m "feat(a4): add Figma MCP server (#41, extract-only — FM1)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Author ADR-004 — A4 design-tooling boundaries
|
||||
|
||||
**Files:** Create `docs/adr/ADR-004-a4-design-tooling-boundaries.md`
|
||||
|
||||
- [ ] **Step 1: Generate the ADR via adr-kit**
|
||||
|
||||
Preferred: run `/adr-kit:adr "A4 design-tooling boundaries"` and let the `adr-generator` agent emit the Nygard 7-section skeleton. Then fill it with the content below.
|
||||
|
||||
- [ ] **Step 2: Write the ADR**
|
||||
|
||||
Create `docs/adr/ADR-004-a4-design-tooling-boundaries.md`:
|
||||
|
||||
```markdown
|
||||
# ADR-004: A4 design-tooling boundaries
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-05-17
|
||||
- **Deciders:** Дмитрий
|
||||
|
||||
## Context
|
||||
The A4 «Дизайн» map section adds three tools to the existing FD #30 / UPM #31 /
|
||||
21st #32: Figma MCP (#41), Universal Icons MCP (#42), Design plugin (#43). Two
|
||||
overlaps with Frontend Design (#30) were identified during selection and must be
|
||||
closed by explicit rule, not left implicit.
|
||||
|
||||
## Decision
|
||||
1. **Figma MCP — extract-only.** Figma MCP is used solely for design-data and
|
||||
design-token reads (e.g. `get_variable_defs`). Its design-to-code generation
|
||||
capability is NOT used. Frontend Design (#30) remains the sole UI solver
|
||||
(PSR_v1 R10.2 — external plugins are read-only for decisions). Figma MCP output
|
||||
is material for FD/Claude, never a substitute solver.
|
||||
2. **Design plugin a11y is design-level, pre-code.** The Design plugin's
|
||||
Accessibility Audit operates at design-critique level. Pa11y remains the single
|
||||
source of truth for technical a11y (CLAUDE.md §5 п.3, PSR_v1 R8 — Pa11y wins on
|
||||
conflict). FD continues to cover a11y-principles during design. Three tiers, no
|
||||
override.
|
||||
3. **Design plugin critique runs in R2 phase 1.** The Design plugin's Design
|
||||
Critique runs in the research / pre-code planning phase, not the phase-8 review.
|
||||
Phase-8 review stays with the PSR_v1 R5 aspect-split (FD owns the UI/UX aspect)
|
||||
plus the Superpowers review skills. The Design plugin does not replace
|
||||
`superpowers:requesting-code-review`.
|
||||
|
||||
## Consequences
|
||||
- A Figma MCP code-generation call is a process violation (CLAUDE.md §5 п.6).
|
||||
- Universal Icons (#42) covers UI icons; 21st `logo_search` covers brand logos —
|
||||
distinct, both retained.
|
||||
- These boundaries are mirrored as PSR_v1 R10.1 rows + R6/R10/R14 notes (Task 6).
|
||||
|
||||
## Enforcement
|
||||
None — role boundaries, verified by code review, not by a regex gate.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Lint and validate**
|
||||
|
||||
```bash
|
||||
npx markdownlint-cli2 "docs/adr/ADR-004-a4-design-tooling-boundaries.md"
|
||||
npx cspell --no-progress --no-summary --no-gitignore "docs/adr/ADR-004-a4-design-tooling-boundaries.md"
|
||||
```
|
||||
|
||||
Add flagged valid terms to `cspell-words.txt`. Then `/adr-kit:lint docs/adr/` — expected: all ADRs pass structural checks.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/adr/ADR-004-a4-design-tooling-boundaries.md cspell-words.txt
|
||||
git commit -m "docs(adr): ADR-004 — A4 design-tooling boundaries (FM1/DP1/DP2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Unified normative registry sync (FM5 / UI4 / DP3)
|
||||
|
||||
**Files:** Modify `docs/Tooling_v8_3.md`, `docs/Plugin_stack_rules_v1.md`, `docs/Pravila_raboty_Claude_v1_1.md`, `CLAUDE.md`, `docs/CHANGELOG_claude_md.md`
|
||||
|
||||
- [ ] **Step 1: Read the registry homes**
|
||||
|
||||
Read for exact insertion points and counters: `docs/Tooling_v8_3.md` Прил. Н §0 (counter "40") + the last subsection §4.15; `docs/Plugin_stack_rules_v1.md` R10.1 (3 blocks); `docs/Pravila_raboty_Claude_v1_1.md` §13.2.
|
||||
|
||||
- [ ] **Step 2: Add Tooling Прил. Н §4.16–§4.18**
|
||||
|
||||
Edit `docs/Tooling_v8_3.md`: add three subsections — `§4.16 #41 Figma MCP`, `§4.17 #42 Universal Icons MCP`, `§4.18 #43 Design plugin`. Category: off-phase **design-tooling** (extends the UI-pool). Per tool:
|
||||
|
||||
- **#41 Figma MCP** — Figma official remote MCP (`https://mcp.figma.com/mcp`, `http`), **extract-only** role per ADR-004 (FM1); R6.0 stack-filter applies to any consumed material (FM4); needs an accessible Figma file (FM2 — note the dependency).
|
||||
- **#42 Universal Icons MCP** — `mcp-universal-icons` (npm, MIT, `awssat`), stdio; UI icons (Lucide-first); default SVG framework-neutral (never request Tailwind/JSX — UI3); distinct from 21st `logo_search` (UI2); bus-factor note (CC1).
|
||||
- **#43 Design plugin** — `anthropics/claude-plugins-official`, Anthropic Verified; design review / a11y critique / UX writing / research synthesis; review-side (no R6.0, outside R14 — like FD); a11y is design-level, Pa11y stays technical SoT (DP1).
|
||||
|
||||
State: Figma MCP + Universal Icons are material tools → R6.0/R6.1 + R14 pipeline apply (same as #32 21st); Design plugin is a review tool → treated like FD. Bump §0 counter `40 → 43`; bump the Прил. Н version header.
|
||||
|
||||
- [ ] **Step 3: Add 3 PSR_v1 R10.1 rows + boundary notes**
|
||||
|
||||
Edit `docs/Plugin_stack_rules_v1.md`: add Figma MCP, Universal Icons MCP, Design plugin rows to R10.1, category **design-tooling** (off-phase). Add the ADR-004 boundary references — Figma MCP extract-only (R10.2), Design plugin a11y/review positioning (R8 / R5). Confirm Figma MCP + Universal Icons fall under R6.0/R6.1/R14 (material-generators, like #32); Design plugin outside R14 (review). Bump the PSR_v1 version header.
|
||||
|
||||
- [ ] **Step 4: Add the Pravila §13.2 note**
|
||||
|
||||
Edit `docs/Pravila_raboty_Claude_v1_1.md` §13.2: add a one-line design-tooling note covering the three tools, alongside the existing infrastructure / architecture-tooling / audit-security notes. Re-read Pravila §0/§13 first to keep section numbering consistent. Bump the Pravila version header.
|
||||
|
||||
- [ ] **Step 5: Update CLAUDE.md via the governed channel**
|
||||
|
||||
Invoke `/claude-md-management:claude-md-improver`. Apply: §3 title count (`40` → `43`), §1 priority-chain row 2b count (`40` → `43`), three new §3.3 rows `#41 Figma MCP` / `#42 Universal Icons MCP` / `#43 Design plugin`, §6 integration paragraph (A4 closure). The plugin also writes the `docs/CHANGELOG_claude_md.md` entry and bumps §0 cross-ref versions (Tooling / PSR_v1 / Pravila).
|
||||
|
||||
- [ ] **Step 6: Lint + commit**
|
||||
|
||||
```bash
|
||||
npx markdownlint-cli2 "docs/Tooling_v8_3.md" "docs/Plugin_stack_rules_v1.md" "docs/Pravila_raboty_Claude_v1_1.md" "docs/CHANGELOG_claude_md.md"
|
||||
npx cspell --no-progress --no-summary --no-gitignore "docs/Tooling_v8_3.md" "docs/Plugin_stack_rules_v1.md" "docs/Pravila_raboty_Claude_v1_1.md"
|
||||
git add docs/Tooling_v8_3.md docs/Plugin_stack_rules_v1.md docs/Pravila_raboty_Claude_v1_1.md CLAUDE.md docs/CHANGELOG_claude_md.md cspell-words.txt
|
||||
git commit -m "docs(a4): register Figma MCP/Universal Icons/Design plugin #41-43 (FM5/UI4/DP3)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Reflect the 3 tools on the map (close A4 — 3→6)
|
||||
|
||||
**Files:** Modify `docs/automation-graph.html`
|
||||
|
||||
- [ ] **Step 1: Read the structures to replicate**
|
||||
|
||||
In `docs/automation-graph.html` read, as templates: an existing MCP-server node — `mcp_21st` — across `NODES`, `NODE_DETAILS`, `NODE_SECTION`, the MCP-серверы group, and the "Паспорт узла" date fields; and an existing plugin node — `fd_plugin` — for the Design-plugin template. Record the current node/edge counts (expected ~106 nodes / ~109 edges after A6) and the MCP-серверы + плагины group sizes.
|
||||
|
||||
- [ ] **Step 2: Add three nodes**
|
||||
|
||||
Add to `NODES`, replicating the template shapes:
|
||||
|
||||
- `mcp_figma` — label `Figma MCP`, MCP-серверы group.
|
||||
- `mcp_icons` — label `Universal Icons MCP`, MCP-серверы group.
|
||||
- `design_plugin` — label `Design plugin`, плагины group.
|
||||
|
||||
Add matching `NODE_DETAILS`: `mcp_figma` — "MCP Figma (extract-only, ADR-004): извлечение токенов/variables из источника дизайна."; `mcp_icons` — "MCP Universal Icons: поиск/вставка SVG-иконок (Lucide-first)."; `design_plugin` — "Плагин Design (Anthropic): дизайн-критика, a11y-аудит, UX-копирайт — pre-code." Паспорт: дата внедрения `2026-05-17` for all three.
|
||||
|
||||
- [ ] **Step 3: Map all three to section A4**
|
||||
|
||||
In `NODE_SECTION` add:
|
||||
|
||||
```js
|
||||
mcp_figma: 'A4', mcp_icons: 'A4', design_plugin: 'A4',
|
||||
```
|
||||
|
||||
A4 «Дизайн (UI/UX, графика, бренд)» goes from 3 → 6 nodes.
|
||||
|
||||
- [ ] **Step 4: Add edges + update header metrics**
|
||||
|
||||
Add edges replicating how `fd_plugin`/`upm`/`mcp_21st` connect (e.g. to governing rules / the design-tooling cluster) — mirror the A6 pattern of 3 nodes / +3 edges. Bump the node count in the map header/legend by 3 (`106 → 109`) and the edge count accordingly. Update the MCP-серверы (+2) and плагины (+1) group-count comments in `NODE_SECTION`.
|
||||
|
||||
- [ ] **Step 5: Smoke-test the map**
|
||||
|
||||
```bash
|
||||
npx stylelint docs/automation-graph.html
|
||||
```
|
||||
|
||||
Open `docs/automation-graph.html` in a browser (Playwright MCP or local `python -m http.server`): 0 JS console errors; the 3 new nodes render; clicking section `A4` highlights all six (FD/UPM/21st + Figma/Icons/Design).
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/automation-graph.html
|
||||
git commit -m "feat(map): add mcp_figma/mcp_icons/design_plugin nodes — closes section A4 (3→6)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Final regression & branch finish
|
||||
|
||||
**Files:** none modified
|
||||
|
||||
- [ ] **Step 1: Full pre-commit chain**
|
||||
|
||||
```bash
|
||||
npx lefthook run pre-commit
|
||||
```
|
||||
|
||||
Expected: all 9 jobs green.
|
||||
|
||||
- [ ] **Step 2: Confirm app code untouched — run the suites**
|
||||
|
||||
These tools change no `app/` code → suites must match the Task 1 baseline:
|
||||
|
||||
```bash
|
||||
cd app && php artisan test --parallel
|
||||
npm run test:vue
|
||||
```
|
||||
|
||||
Expected: Pest and Vitest counts unchanged vs the Task 1 baseline (0 regressions). Record exact counts; write out any failure with file:line.
|
||||
|
||||
- [ ] **Step 3: Confirm the economy/ruflo hook chain is intact**
|
||||
|
||||
Economy marker still appears; Stop verifier still runs; no plugin/server leaked a `hooks` entry into either `settings.json` (DP4/FM6/UI3 final check).
|
||||
|
||||
- [ ] **Step 4: Pre-push checks**
|
||||
|
||||
```bash
|
||||
./bin/gitleaks.exe detect --source . --no-banner --config .gitleaks.toml --redact
|
||||
./bin/lychee.exe --config .lychee.toml "docs/**/*.md" "db/**/*.md" "*.md"
|
||||
```
|
||||
|
||||
Expected: gitleaks 0 leaks (FM3 — confirm no Figma token leaked); lychee 0 broken (the new `docs/adr/ADR-004*.md` is scanned — fix or `.lychee.toml`-exclude any link).
|
||||
|
||||
- [ ] **Step 5: Finish the branch**
|
||||
|
||||
Invoke `superpowers:finishing-a-development-branch` — present the standard options. Do **not** push without an explicit user choice.
|
||||
|
||||
---
|
||||
|
||||
## Self-Review
|
||||
|
||||
**1. Spec coverage** — A4 gap analysis: графика → #42 (Task 3), бренд/источник → #41 (Task 4), design-review → #43 (Task 2). Conflict audit: FM1→ADR-004 D1 (Task 5) + PSR_v1 row (Task 6.3); FM2→Task 1.5 hard gate; FM3→Task 4.1; FM4→Task 6.2; FM5/UI4/DP3→Task 6; FM6/UI3/DP4→install-task verify steps; UI1→no task (no conflict); UI2→ADR-004 + Tooling note; DP1→ADR-004 D2; DP2→ADR-004 D3; DP5→no task; CC1→Tooling §4.x note. Map A4 closure→Task 7. No gaps.
|
||||
|
||||
**2. Placeholder scan** — Task 1 Step 4/Step 5 and Task 7 Step 1 are *fact-check / decision / read-template* steps with concrete criteria, not "TBD" (exact MCP-add syntax, the FM2 prerequisite, and the live 2400-line map node shapes are not knowable without install / without reading the file). All commands exact; ADR-004 content shown in full; the `.mcp.json` blocks shown in full.
|
||||
|
||||
**3. Consistency** — registry numbers `#41 Figma / #42 Universal Icons / #43 Design` consistent across Tasks 5-7; counter `40 → 43` consistent Task 6.2↔6.5; node ids `mcp_figma`/`mcp_icons`/`design_plugin` consistent Task 7 Steps 2-3; map count `106 → 109` consistent; ADR id `ADR-004` consistent Task 5↔6↔7; "extract-only" wording for Figma MCP consistent FM1↔ADR-004↔Tooling↔map.
|
||||
|
||||
---
|
||||
|
||||
## Execution Handoff
|
||||
|
||||
Plan complete and saved to `docs/superpowers/plans/2026-05-17-a4-design-tooling-integration.md`. Two execution options:
|
||||
|
||||
1. **Subagent-Driven** — fresh subagent per task, two-stage review. *Caveat:* Tasks 2-5 (slash commands `/plugin …`, `/reload-plugins`, `claude mcp add`, `/adr-kit:adr`, `/adr-kit:lint`), Task 4 Step 2 (Figma OAuth) and Task 6 Step 5 (`claude-md-management`) are main-session-bound — those steps stay with the controller.
|
||||
2. **Inline Execution** — `superpowers:executing-plans`, batch with checkpoints. **Recommended here** — the integration is install/config/docs-heavy with many interactive main-session steps and one user-decision gate (FM2).
|
||||
|
||||
Open gate for the user: the **FM2 spike (Task 1 Step 5)** decides full 6-node (`#41` included) vs 5-node (`#41` deferred). Surface the result before Task 4.
|
||||
@@ -0,0 +1,555 @@
|
||||
# deptrac Architecture-Fitness Integration Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Close the four open architecture-fitness gaps inside map section A6 — active feature-architecture design, architecture conformance, layer/dependency-direction enforcement, and C4-component-diagram drift — by integrating **deptrac** as a declarative, zero-LLM layer-dependency gate wired into the lefthook pre-commit chain.
|
||||
|
||||
**Architecture:** A6 «Архитектура систем» currently has three nodes (adr-kit #36 / mermaid-skill #37 / architecture-patterns #38) covering *record / visualize / reference* — but nothing **enforces** that code keeps matching the chosen layered architecture beyond `adr-judge`'s narrow regex scope. deptrac is a deterministic static-analysis tool (no LLM, matches the AK6 principle adr-kit was built under): it parses the `App\` source into named layers (Controller / Service / Model / Job / …) and fails the build on disallowed dependency directions. Pre-existing violations are captured once into a baseline so the gate is green from day one and only catches *new* drift. deptrac's code-derived graph output doubles as a drift-proof C4-component diagram. deptrac becomes the 4th architecture-tooling tool — A6 goes 3 → 4 nodes.
|
||||
|
||||
**Tech Stack:** deptrac (`qossmic/deptrac` — PHP static analysis, declarative YAML, BSD-3); composer dev-dependency in `app/` (PHAR-in-`bin/` fallback — DT1); lefthook (pre-commit job 10); the project normative docs; `docs/automation-graph.html` (vis.js).
|
||||
|
||||
---
|
||||
|
||||
## Sequencing (locked 2026-05-17)
|
||||
|
||||
This is the **4th** tooling-integration epic. The A6, D3, and C9 plans lock a strict, never-interleaved order because all four epics touch the same shared files (the map, the 4 normative docs, the Tooling counter, `cspell-words.txt`). deptrac joins that queue:
|
||||
|
||||
- **A6** architecture-tooling — ✅ complete, on `origin/main`.
|
||||
- **D3** audit-risk-tooling — must run to completion and push **first**.
|
||||
- **C9** project-management-tooling — runs after D3.
|
||||
- **deptrac** (this plan) — runs after D3; its slot **relative to C9** (before or after) is the user's call at the Execution Handoff. Both orders are valid — deptrac shares no logical dependency with C9.
|
||||
|
||||
Task 1 forks the working branch from the **then-current** `origin/main` (not hard-coded). The Tooling registry number is **runtime-resolved** (DT-NUM) — never hard-code a number before reading the live counter; A6 took #36-38, D3 claims #39-40, C9 claims #41(-42).
|
||||
|
||||
---
|
||||
|
||||
## Tool Identity (verified 2026-05-17 — re-verify in Task 1)
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| Tool | **deptrac** — PHP architecture layer/dependency enforcement |
|
||||
| Package | `qossmic/deptrac` (Composer) — **Task 1 re-confirms the current package name, major version, PHP 8.3 / Laravel 13 compatibility, and the collector-config syntax** |
|
||||
| License | BSD-3-Clause (permissive) |
|
||||
| Install mode | Composer **dev-dependency** in `app/`; **fallback** = PHAR in `bin/deptrac.phar` if the dependency resolver conflicts (DT1) |
|
||||
| Hooks | None — deptrac is a CLI tool, registers no Claude Code lifecycle hooks |
|
||||
| LLM cost | Zero — pure static analysis (AST), no API calls (AK6-aligned) |
|
||||
| Category | off-phase, **architecture-tooling** — 4th tool of that subcategory (with #36-38) |
|
||||
|
||||
**Out of scope (deliberate — YAGNI).** JS-side module-boundary enforcement for `resources/js` (`eslint-plugin-boundaries` / `dependency-cruiser`) is a separate future epic. This plan closes the **PHP/backend** architecture-fitness gaps only. Context/container-level C4 drift (external systems — Yandex Cloud, Unisender, JivoSite, Sentry) is inherently hand-maintained; it is covered by a §8 self-review checklist line (Task 7), not by tooling.
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions & Conflict Audit
|
||||
|
||||
Pattern follows the AK1-AK6 (A6) / TB1 (D3) / CP-PG (C9) audits. Verified against `lefthook.yml`, `app/composer.json`, the `app/app/` tree, `app/phpstan.neon`, and the A6/D3/C9 plans.
|
||||
|
||||
| # | Sev | Conflict | Resolution (locked) |
|
||||
|---|---|---|---|
|
||||
| DT1 | 🟡 | `composer require --dev qossmic/deptrac` may conflict with Laravel 13's dependency tree (deptrac historically ships isolated). | Task 2: try the composer dev-dep first. If the resolver conflicts → fallback to a pinned `bin/deptrac.phar` (the project already vendors `bin/gitleaks.exe`, `bin/squawk.exe`, `bin/lychee.exe` this way). Decision criterion + both paths are in Task 2. |
|
||||
| DT2 | 🟢 | deptrac becomes the 10th pre-commit job — adds latency per commit. | deptrac on a codebase this size analyses in ~1-3 s. Measured in Task 5 Step 4; recorded. `glob: "app/**/*.php"` scopes the job to PHP changes only. |
|
||||
| DT3 | 🟡 | The **first** `deptrac analyse` will report pre-existing layer violations in current `app/` code → job 10 would block **every** commit. | **The headline risk.** Task 4 generates `app/deptrac.baseline.yaml` (`deptrac analyse --formatter=baseline`) capturing all current violations; job 10 then fails only on **new** drift. Same philosophy as `app/phpstan-baseline.neon`. The baseline count is recorded as architecture debt in `docs/architecture/`. |
|
||||
| DT4 | 🟢 | Overlap with Larastan #12 (§5 п.6 — no two tools per task). | Different tasks: Larastan = type-level bug detection (PHPStan); deptrac = layer-dependency graph. Not a duplicate. Stated explicitly in the Tooling entry (Task 7). |
|
||||
| DT5 | 🟢 | Overlap with adr-kit's `adr-judge` #36 — both "architecture enforcement" in lefthook. | Complementary, different mechanism: `adr-judge` = declarative regex on ADR `## Enforcement` text blocks; deptrac = AST-level layer-dependency graph. Documented in the Tooling entry + an ADR (Task 3 Step 5). |
|
||||
| DT6 | 🟢 | The project nests Laravel at `app/`, so the `App\` source is `app/app/`. | deptrac config lives at `app/deptrac.yaml`; `paths: [app]` resolves to `app/app/`. lefthook job 10 uses `root: "app/"`. |
|
||||
| DT7 | 🟢 | New `deptrac.yaml` / `deptrac.baseline.yaml` files. | `.yaml` — no pre-commit lint job touches it (squawk = `*.sql`, cspell/markdownlint = `*.md`). No lint conflict. |
|
||||
| DT8 | 🟢 | Native Windows (no Docker / nested-virt — see `project_phase1_strategy`). | deptrac is pure PHP, runs via `php`. No extension/virtualization dependency (unlike pg_partman). |
|
||||
| DT-NUM | 🟡 | A6 = #36-38, D3 = #39-40, C9 = #41(-42). deptrac must not collide. | Task 7 reads the **live** `docs/Tooling_v8_3.md` Прил. Н §0 counter and assigns `counter + 1`. Never hard-code. |
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Created / Modified | Responsibility |
|
||||
|---|---|---|
|
||||
| `app/deptrac.yaml` | Create | deptrac layer definitions + dependency ruleset |
|
||||
| `app/deptrac.baseline.yaml` | Create | Baseline of pre-existing violations (DT3) |
|
||||
| `app/composer.json` + `app/composer.lock` | Modify | deptrac dev-dependency (DT1 primary path) |
|
||||
| `bin/deptrac.phar` | Create *(conditional — DT1 fallback only)* | Vendored deptrac PHAR if the composer resolver conflicts |
|
||||
| `lefthook.yml` | Modify | New pre-commit **job 10** `deptrac` |
|
||||
| `docs/architecture/c4-component-layers.md` | Create | Code-derived layer/component diagram (gap 4) + baseline-debt note |
|
||||
| `docs/adr/ADR-005-architecture-fitness-deptrac.md` | Create | ADR recording the layer model + deptrac decision (reuses adr-kit from A6) |
|
||||
| `docs/Tooling_v8_3.md` | Modify | Прил. Н — new architecture-tooling subsection + §0 counter bump |
|
||||
| `docs/Plugin_stack_rules_v1.md` | Modify | R10.1 Блок 1 — new deptrac row |
|
||||
| `docs/Pravila_raboty_Claude_v1_1.md` | Modify | §13.2 — extend the architecture-tooling category note |
|
||||
| `CLAUDE.md` | Modify (**via claude-md-management only** — §5 п.10) | §3 title count, §1 row 2b count, new §3.3 row, §6 paragraph |
|
||||
| `docs/CHANGELOG_claude_md.md` | Modify | CLAUDE.md version-bump entry |
|
||||
| `docs/automation-graph.html` | Modify | New `deptrac` node → `NODE_SECTION` A6; header metrics |
|
||||
| `cspell-words.txt` | Modify | deptrac vocabulary (`deptrac`, `qossmic`, `Deptrac`, …) |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Pre-flight — branch, baseline, fact-check, spike
|
||||
|
||||
**Files:** none modified (read-only) except a new branch.
|
||||
|
||||
- [ ] **Step 1: Confirm tree state and create the working branch**
|
||||
|
||||
```bash
|
||||
cd "c:/моя/проекты/портал crm/Документация"
|
||||
git status --short
|
||||
git fetch origin
|
||||
git checkout -b feat/deptrac-architecture-fitness origin/main
|
||||
git rev-parse --short HEAD
|
||||
```
|
||||
|
||||
Expected: D3 (and, per the user's chosen slot, C9) are already on `origin/main`. Record the `origin/main` HEAD SHA as the regression baseline. Push pattern at the end: `git push origin feat/deptrac-architecture-fitness:main`.
|
||||
|
||||
- [ ] **Step 2: Baseline regression**
|
||||
|
||||
Run `/regression quick`. Expected: GREEN. Record the last green Pest / Vitest counts from memory `project_state.md`. This epic changes **no** `app/` runtime code → the Task 9 run must match exactly.
|
||||
|
||||
- [ ] **Step 3: Snapshot the hook chain**
|
||||
|
||||
Read `.claude/settings.json` and `~/.claude/settings.json`; record every lifecycle hook. deptrac registers none — this snapshot is the Task 9 comparison baseline.
|
||||
|
||||
- [ ] **Step 4: Fact-check deptrac (DT1)**
|
||||
|
||||
WebFetch `https://github.com/qossmic/deptrac` and the Packagist page. Confirm and record:
|
||||
|
||||
- Current Composer package name + latest major version.
|
||||
- PHP 8.3 / Laravel 13 compatibility (deptrac analyses source, does not load the app — compatibility is just the PHP runtime version).
|
||||
- The collector config syntax for the installed major (`type: directory` path-regex vs `type: classLike` namespace-regex) — Task 3 writes the config for **this** syntax.
|
||||
- The available `--formatter` values (specifically whether `mermaidjs` exists — Task 6 uses it; fallback `graphviz`).
|
||||
|
||||
If the current major's config schema differs materially from the Task 3 example below → adjust Task 3's config to match; note the deviation.
|
||||
|
||||
- [ ] **Step 5: Spike — run deptrac once on the real codebase**
|
||||
|
||||
Install deptrac into a throwaway location (do **not** commit yet) and run it once to learn the real violation count — this sizes DT3:
|
||||
|
||||
```bash
|
||||
cd app
|
||||
composer require --dev qossmic/deptrac --no-interaction --dry-run
|
||||
```
|
||||
|
||||
Record whether `--dry-run` reports a dependency conflict (drives Task 2's path). Then do a real throwaway install + a single `vendor/bin/deptrac analyse` against a *minimal* temp config (one `Service`/`Model` layer pair) just to confirm the binary runs on Windows and prints a violation report. Record the rough violation count. Revert the throwaway install (`git checkout app/composer.json app/composer.lock`) — Task 2 does the real, committed install.
|
||||
|
||||
No repo files committed in Task 1.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Install deptrac
|
||||
|
||||
**Files:** Modify `app/composer.json`, `app/composer.lock` — **or** Create `bin/deptrac.phar` (DT1 fallback).
|
||||
|
||||
- [ ] **Step 1: Install — primary path (composer dev-dependency)**
|
||||
|
||||
If Task 1 Step 5 reported **no** resolver conflict:
|
||||
|
||||
```bash
|
||||
cd app
|
||||
composer require --dev qossmic/deptrac --no-interaction
|
||||
php vendor/bin/deptrac --version
|
||||
```
|
||||
|
||||
Expected: deptrac added under `require-dev` in `app/composer.json`; `vendor/bin/deptrac` exists; `--version` prints the version. Record it.
|
||||
|
||||
- [ ] **Step 2: Install — fallback path (PHAR in `bin/`)**
|
||||
|
||||
Run this **only if** Step 1's `composer require` failed the dependency resolver. Download the pinned PHAR release asset from `https://github.com/qossmic/deptrac/releases` into `bin/deptrac.phar`:
|
||||
|
||||
```bash
|
||||
curl -L -o bin/deptrac.phar https://github.com/qossmic/deptrac/releases/download/<version>/deptrac.phar
|
||||
php bin/deptrac.phar --version
|
||||
```
|
||||
|
||||
Expected: `php bin/deptrac.phar --version` prints the version. Record which path (Step 1 or Step 2) was taken — every later `deptrac` invocation in this plan uses `php vendor/bin/deptrac` (Step 1) **or** `php ../bin/deptrac.phar` run from `app/` (Step 2).
|
||||
|
||||
- [ ] **Step 3: Verify the offline security audit still passes**
|
||||
|
||||
```bash
|
||||
cd app && composer audit --locked
|
||||
```
|
||||
|
||||
Expected: 0 advisories (Roave already gates installs; this confirms deptrac added none).
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
Primary path:
|
||||
|
||||
```bash
|
||||
git add app/composer.json app/composer.lock
|
||||
git commit -m "build(deptrac): add deptrac as a composer dev-dependency (DT1)"
|
||||
```
|
||||
|
||||
Fallback path:
|
||||
|
||||
```bash
|
||||
git add bin/deptrac.phar
|
||||
git commit -m "build(deptrac): vendor deptrac PHAR into bin/ (DT1 fallback)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Author `deptrac.yaml` — layers + conservative ruleset
|
||||
|
||||
**Files:** Create `app/deptrac.yaml`; Create `docs/adr/ADR-005-architecture-fitness-deptrac.md`.
|
||||
|
||||
- [ ] **Step 1: Write the deptrac config**
|
||||
|
||||
Create `app/deptrac.yaml` (adjust the collector `type` to the syntax recorded in Task 1 Step 4 — the example uses the `type: directory` path-regex form):
|
||||
|
||||
```yaml
|
||||
deptrac:
|
||||
paths:
|
||||
- ./app
|
||||
layers:
|
||||
- name: Controller
|
||||
collectors: [{ type: directory, value: app/Http/Controllers/.* }]
|
||||
- name: Request
|
||||
collectors: [{ type: directory, value: app/Http/Requests/.* }]
|
||||
- name: Resource
|
||||
collectors: [{ type: directory, value: app/Http/Resources/.* }]
|
||||
- name: Middleware
|
||||
collectors: [{ type: directory, value: app/Http/Middleware/.* }]
|
||||
- name: Service
|
||||
collectors: [{ type: directory, value: app/Services/.* }]
|
||||
- name: Job
|
||||
collectors: [{ type: directory, value: app/Jobs/.* }]
|
||||
- name: Console
|
||||
collectors: [{ type: directory, value: app/Console/.* }]
|
||||
- name: Repository
|
||||
collectors: [{ type: directory, value: app/Repositories/.* }]
|
||||
- name: Model
|
||||
collectors: [{ type: directory, value: app/Models/.* }]
|
||||
- name: Mail
|
||||
collectors: [{ type: directory, value: app/Mail/.* }]
|
||||
- name: Rule
|
||||
collectors: [{ type: directory, value: app/Rules/.* }]
|
||||
- name: Exception
|
||||
collectors: [{ type: directory, value: app/Exceptions/.* }]
|
||||
- name: Provider
|
||||
collectors: [{ type: directory, value: app/Providers/.* }]
|
||||
ruleset:
|
||||
# Conservative ruleset — enforces only the architecturally-wrong directions
|
||||
# (inward/upward deps). Whatever current code violates is captured by the
|
||||
# Task 4 baseline; this gate then catches only NEW drift.
|
||||
Controller: [Service, Request, Resource, Model, Job, Mail, Repository, Rule, Exception]
|
||||
Middleware: [Service, Model, Exception]
|
||||
Service: [Service, Model, Repository, Job, Mail, Rule, Exception]
|
||||
Job: [Service, Model, Repository, Mail, Exception]
|
||||
Console: [Service, Model, Repository, Job, Mail, Exception]
|
||||
Repository: [Model, Exception]
|
||||
Request: [Rule, Model]
|
||||
Resource: [Model]
|
||||
Rule: [Model]
|
||||
Mail: [Model]
|
||||
Model: ~ # bottom layer — depends on nothing in App\
|
||||
Provider: [Controller, Service, Job, Console, Repository, Model, Mail, Middleware, Request, Resource, Rule, Exception]
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run deptrac — see the current state (the "failing test")**
|
||||
|
||||
Primary: `cd app && php vendor/bin/deptrac analyse --no-progress` (fallback: `cd app && php ../bin/deptrac.phar analyse --no-progress`).
|
||||
Expected: deptrac parses the config and prints a violation report. If the config has a **schema error**, deptrac prints the offending key — fix per its message and re-run. Record the violation count + the uncovered-class count. A non-zero violation count here is expected and correct (it is the architecture debt the baseline will capture).
|
||||
|
||||
- [ ] **Step 3: Write ADR-005**
|
||||
|
||||
Create `docs/adr/ADR-005-architecture-fitness-deptrac.md` (Nygard format — mirror the shape of `docs/adr/ADR-000`..`ADR-003`):
|
||||
|
||||
```markdown
|
||||
# ADR-005: Architecture-fitness enforcement via deptrac
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-05-17
|
||||
- **Deciders:** Дмитрий
|
||||
|
||||
## Context
|
||||
Map section A6 had tools to *record* (adr-kit), *visualize* (mermaid-skill) and
|
||||
*reference* (architecture-patterns) architecture — but nothing enforced that the
|
||||
code keeps matching the layered architecture (Controller → Service → Model …).
|
||||
`adr-judge` enforces only what is hand-written as a regex in an ADR Enforcement
|
||||
block — too narrow for dependency-direction rules.
|
||||
|
||||
## Decision
|
||||
- Adopt **deptrac** — a declarative, zero-LLM static-analysis tool — as the
|
||||
layer-dependency gate, wired as lefthook pre-commit job 10.
|
||||
- The layer model and ruleset are defined in `app/deptrac.yaml` (conservative —
|
||||
enforces only inward/upward-violating directions).
|
||||
- Pre-existing violations are captured in `app/deptrac.baseline.yaml`; the gate
|
||||
fails only on NEW drift. Reducing the baseline is tracked architecture debt.
|
||||
|
||||
## Consequences
|
||||
- Positive: layer drift is caught at commit time, deterministically, free.
|
||||
- Risk: a too-strict ruleset produces noise — mitigated by the conservative
|
||||
ruleset + the baseline.
|
||||
|
||||
## Enforcement
|
||||
The layer rules live in `app/deptrac.yaml`, enforced by lefthook job 10 — not by
|
||||
an `adr-judge` regex. This ADR has no `adr-judge` Enforcement clause.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Lint + commit**
|
||||
|
||||
```bash
|
||||
npx markdownlint-cli2 "docs/adr/ADR-005-*.md"
|
||||
npx cspell --no-progress --no-summary --no-gitignore "docs/adr/ADR-005-*.md"
|
||||
```
|
||||
|
||||
Add valid flagged terms (`deptrac`, `qossmic`, `Nygard`, …) to `cspell-words.txt`. Then:
|
||||
|
||||
```bash
|
||||
git add app/deptrac.yaml docs/adr/ADR-005-*.md cspell-words.txt
|
||||
git commit -m "feat(deptrac): layer model + ruleset config + ADR-005"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Generate the baseline (DT3 — the headline risk)
|
||||
|
||||
**Files:** Create `app/deptrac.baseline.yaml`; Modify `app/deptrac.yaml`.
|
||||
|
||||
- [ ] **Step 1: Generate the baseline file**
|
||||
|
||||
```bash
|
||||
cd app && php vendor/bin/deptrac analyse --formatter=baseline --output=deptrac.baseline.yaml
|
||||
```
|
||||
|
||||
(Fallback binary: `php ../bin/deptrac.phar …`.) Expected: `app/deptrac.baseline.yaml` is created, listing every current violation as a skipped entry.
|
||||
|
||||
- [ ] **Step 2: Wire the baseline into the config**
|
||||
|
||||
Edit `app/deptrac.yaml` — add under the `deptrac:` key:
|
||||
|
||||
```yaml
|
||||
baseline_file: ./deptrac.baseline.yaml
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run deptrac — confirm GREEN (the "passing test")**
|
||||
|
||||
```bash
|
||||
cd app && php vendor/bin/deptrac analyse --no-progress
|
||||
```
|
||||
|
||||
Expected: **0 reported violations** (all pre-existing ones absorbed by the baseline). Exit code 0. If any violation still reports → the baseline did not capture it; regenerate (Step 1) and re-check.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add app/deptrac.yaml app/deptrac.baseline.yaml
|
||||
git commit -m "feat(deptrac): baseline pre-existing violations — gate green from day 1 (DT3)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Wire lefthook pre-commit job 10
|
||||
|
||||
**Files:** Modify `lefthook.yml`.
|
||||
|
||||
- [ ] **Step 1: Add job 10**
|
||||
|
||||
Edit `lefthook.yml` — append after job 9 (`adr-judge`), inside `pre-commit.jobs`:
|
||||
|
||||
```yaml
|
||||
# 10. deptrac — архитектурный гейт направления зависимостей / границ слоёв
|
||||
# (Прил. Н — architecture-tooling). Анализирует app/app/** по слоям из
|
||||
# app/deptrac.yaml; baseline_file гасит унаследованные нарушения (DT3) —
|
||||
# job падает только на НОВОМ дрейфе. Без glob точечного анализа: deptrac
|
||||
# строит граф классов, нужен весь app/. Чистый PHP, без LLM (AK6).
|
||||
- name: deptrac
|
||||
glob: "app/**/*.php"
|
||||
root: "app/"
|
||||
run: php vendor/bin/deptrac analyse --no-progress
|
||||
fail_text: |
|
||||
deptrac: staged-изменение нарушает направление зависимостей между
|
||||
слоями (см. file:line выше и app/deptrac.yaml ruleset).
|
||||
Если это осознанное архитектурное изменение — сначала обнови
|
||||
app/deptrac.yaml (и при необходимости ADR-005), затем коммить.
|
||||
```
|
||||
|
||||
Fallback-PHAR variant of the `run:` line: `run: php ../bin/deptrac.phar analyse --no-progress` — use whichever Task 2 path was taken.
|
||||
|
||||
- [ ] **Step 2: Verify the full chain runs**
|
||||
|
||||
```bash
|
||||
npx lefthook run pre-commit
|
||||
```
|
||||
|
||||
Expected: all 10 jobs execute; job 10 `deptrac` runs and reports 0 violations (baseline applied). Record job 10's wall-time (DT2).
|
||||
|
||||
- [ ] **Step 3: Red-green — prove the gate actually catches drift**
|
||||
|
||||
Introduce a deliberate violation, confirm deptrac flags it, then revert:
|
||||
|
||||
```bash
|
||||
# pick a Model and make it import a Service (a forbidden upward dependency)
|
||||
```
|
||||
|
||||
Edit any file under `app/app/Models/` to add `use App\Services\NotificationService;` and reference it. Run `cd app && php vendor/bin/deptrac analyse --no-progress`.
|
||||
Expected: **deptrac reports 1 violation** (`Model must not depend on Service`), exit code non-zero. Then revert the edit (`git checkout app/app/Models/<file>`) and re-run — expected 0 violations again. This is the red-green proof that job 10 works.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add lefthook.yml
|
||||
git commit -m "ci(deptrac): wire deptrac as lefthook pre-commit job 10"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Code-derived C4-component diagram (gap 4)
|
||||
|
||||
**Files:** Create `docs/architecture/c4-component-layers.md`; Modify the `docs/architecture/` index.
|
||||
|
||||
- [ ] **Step 1: Generate the layer graph from code**
|
||||
|
||||
If Task 1 Step 4 confirmed a `mermaidjs` formatter:
|
||||
|
||||
```bash
|
||||
cd app && php vendor/bin/deptrac analyse --formatter=mermaidjs --output=../docs/architecture/_deptrac-layers.mmd
|
||||
```
|
||||
|
||||
Else use `--formatter=graphviz-dot --output=../docs/architecture/_deptrac-layers.dot`. The graph is **derived from the actual code** — it cannot drift.
|
||||
|
||||
- [ ] **Step 2: Create the diagram doc**
|
||||
|
||||
Create `docs/architecture/c4-component-layers.md`: a short intro, then the generated Mermaid graph embedded in a ```mermaid fence (or, for the graphviz fallback, a note + the committed `.dot` file). Include the recorded baseline violation count (Task 4 Step 1) as a one-line "architecture debt" figure. State that this is the **component-level** C4 view, derived from `app/deptrac.yaml`, regenerate via the Task 6 Step 1 command — and that **context/container-level** C4 (`c4-context.md`, external systems) stays hand-maintained.
|
||||
|
||||
- [ ] **Step 3: Link it from the architecture index**
|
||||
|
||||
Read the `docs/architecture/` index/README created by the A6 epic; add a link to `c4-component-layers.md` next to `c4-context.md`.
|
||||
|
||||
- [ ] **Step 4: Lint + commit**
|
||||
|
||||
```bash
|
||||
npx markdownlint-cli2 "docs/architecture/c4-component-layers.md"
|
||||
npx cspell --no-progress --no-summary --no-gitignore "docs/architecture/c4-component-layers.md"
|
||||
git add docs/architecture/
|
||||
git commit -m "docs(arch): code-derived C4 component-layer diagram from deptrac (gap 4)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Normative registry sync (DT-NUM)
|
||||
|
||||
**Files:** Modify `docs/Tooling_v8_3.md`, `docs/Plugin_stack_rules_v1.md`, `docs/Pravila_raboty_Claude_v1_1.md`, `CLAUDE.md`, `docs/CHANGELOG_claude_md.md`, `cspell-words.txt`.
|
||||
|
||||
- [ ] **Step 1: Read the registry homes + the live counter (DT-NUM)**
|
||||
|
||||
Read for exact insertion points and the **current** counter: `docs/Tooling_v8_3.md` Прил. Н §0 + the last `§4.x` subsection (architecture-tooling = §4.11-4.13); `docs/Plugin_stack_rules_v1.md` R10.1 Блок 1; `docs/Pravila_raboty_Claude_v1_1.md` §13.2. Assign deptrac the number `counter + 1` — **after** the A6/D3/C9 entries already present. Record `#N`.
|
||||
|
||||
- [ ] **Step 2: Add the Tooling Прил. Н subsection**
|
||||
|
||||
Edit `docs/Tooling_v8_3.md`: add a `§4.x` subsection for `#N deptrac`, category **architecture-tooling** (off-phase — the 4th tool of that subcategory, with adr-kit/mermaid/architecture-patterns). Record: `qossmic/deptrac` BSD-3; install mode (composer dev-dep or `bin/deptrac.phar` — whichever Task 2 took); config `app/deptrac.yaml` + `app/deptrac.baseline.yaml`; lefthook job 10; zero-LLM (AK6-aligned); the DT4 boundary vs Larastan (#12 — type analysis ≠ layer graph) and DT5 vs adr-judge (#36 — regex ≠ AST graph). Bump the §0 counter and the Прил. Н version header. Mirror the shape of the §4.11 adr-kit entry.
|
||||
|
||||
- [ ] **Step 3: Add the PSR_v1 R10.1 row**
|
||||
|
||||
Edit `docs/Plugin_stack_rules_v1.md`: add a deptrac row to R10.1 Блок 1, category **architecture-tooling** — explicitly *outside* the UI-pool → no R6.0/R6.1 stack-filter, no R14 pipeline (same treatment as adr-kit/architecture-patterns). Bump the PSR_v1 version header.
|
||||
|
||||
- [ ] **Step 4: Extend the Pravila §13.2 note**
|
||||
|
||||
Edit `docs/Pravila_raboty_Claude_v1_1.md` §13.2: the **architecture-tooling** category note already exists (added by A6 for #36-38) — extend it to include `#N deptrac`. Re-read Pravila §0/§13 first to keep numbering consistent. Bump the Pravila version header.
|
||||
|
||||
- [ ] **Step 5: Update CLAUDE.md via the governed channel**
|
||||
|
||||
Invoke `/claude-md-management:claude-md-improver`. Apply: §3 title count bump (+1); §1 priority-chain row 2b count bump (+1); a new §3.3 row for `#N deptrac` (architecture-tooling, lefthook job 10, mirrors the #36 adr-kit row); a §6 paragraph noting the deptrac architecture-fitness integration. The plugin also writes the `docs/CHANGELOG_claude_md.md` entry and bumps the §0 cross-ref versions. **Do not** edit `CLAUDE.md` directly (§5 п.10).
|
||||
|
||||
- [ ] **Step 6: Lint + commit**
|
||||
|
||||
```bash
|
||||
npx markdownlint-cli2 "docs/Tooling_v8_3.md" "docs/Plugin_stack_rules_v1.md" "docs/Pravila_raboty_Claude_v1_1.md" "docs/CHANGELOG_claude_md.md"
|
||||
npx cspell --no-progress --no-summary --no-gitignore "docs/Tooling_v8_3.md" "docs/Plugin_stack_rules_v1.md" "docs/Pravila_raboty_Claude_v1_1.md"
|
||||
git add docs/Tooling_v8_3.md docs/Plugin_stack_rules_v1.md docs/Pravila_raboty_Claude_v1_1.md CLAUDE.md docs/CHANGELOG_claude_md.md cspell-words.txt
|
||||
git commit -m "docs(deptrac): register #N deptrac architecture-tooling (DT-NUM)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Reflect deptrac on the map — extend section A6
|
||||
|
||||
**Files:** Modify `docs/automation-graph.html`.
|
||||
|
||||
- [ ] **Step 1: Read the structures to replicate**
|
||||
|
||||
In `docs/automation-graph.html` read, as templates, the A6 node `adr_kit` across `NODES`, `NODE_DETAILS`/`nd(...)`, `NODE_META`, `NODE_SECTION`, and the lefthook-job nodes (`lh_larastan`) — deptrac is both an architecture-tooling node and a lefthook job. Record the current node/edge counts from the header comment.
|
||||
|
||||
- [ ] **Step 2: Add the `deptrac` node**
|
||||
|
||||
Add to `NODES` a `deptrac` node replicating the `adr_kit` shape (plugins/tooling group, `ring`/`pos` near the other A6 nodes). Add a matching `nd(...)` / `NODE_DETAILS` entry (Russian, per the file's convention) and a `NODE_META` entry with `since: '17.05.2026'`:
|
||||
> "deptrac — архитектурный гейт: статический анализ направления зависимостей между слоями (Controller/Service/Model/…), lefthook job 10, baseline на унаследованные нарушения. Декларативно, без LLM (AK6). Закрывает A6-пробелы conformance + границы слоёв + дрейф C4."
|
||||
|
||||
Add an edge from `tooling` (or `psr_v1`) to `deptrac`, mirroring the A6 edges.
|
||||
|
||||
- [ ] **Step 3: Map the node to section A6**
|
||||
|
||||
In `NODE_SECTION` add `deptrac: 'A6',`. Section A6 «Архитектура систем» goes 3 → 4 nodes. Update the group-count comment (plugins/tooling) and the header node/edge metrics (+1 node, +1 edge).
|
||||
|
||||
- [ ] **Step 4: Smoke-test the map**
|
||||
|
||||
```bash
|
||||
npx stylelint docs/automation-graph.html
|
||||
```
|
||||
|
||||
Open `docs/automation-graph.html` via a local `http.server` (quirk 90 — `file://` rejected) with Playwright MCP. Verify: 0 JS console errors (favicon 404 is harmless); `NODES.length` increased by 1; the `deptrac` node renders; clicking section A6 highlights 4 nodes.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/automation-graph.html
|
||||
git commit -m "feat(map): deptrac node — extends section A6 to 4 nodes"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Final regression & branch finish
|
||||
|
||||
**Files:** none modified.
|
||||
|
||||
- [ ] **Step 1: Full pre-commit chain**
|
||||
|
||||
```bash
|
||||
npx lefthook run pre-commit
|
||||
```
|
||||
|
||||
Expected: all **10** jobs green (job 10 `deptrac` = 0 violations, baseline applied).
|
||||
|
||||
- [ ] **Step 2: Confirm app runtime code untouched — run the suites**
|
||||
|
||||
This epic adds only `deptrac.yaml` + a composer dev-dep + docs; it changes **no** `app/` runtime code:
|
||||
|
||||
```bash
|
||||
cd app && composer test:parallel
|
||||
npm run test:vue
|
||||
```
|
||||
|
||||
Expected: Pest and Vitest counts **identical** to the Task 1 Step 2 baseline (0 regressions). Record exact counts; write out any failure with file:line.
|
||||
|
||||
- [ ] **Step 3: Confirm the hook chain is intact**
|
||||
|
||||
Compare `.claude/settings.json` + `~/.claude/settings.json` to the Task 1 Step 3 snapshot — unchanged. The economy marker still appears; the Stop verifier still runs. deptrac leaked no lifecycle hook.
|
||||
|
||||
- [ ] **Step 4: Pre-push checks**
|
||||
|
||||
```bash
|
||||
./bin/gitleaks.exe detect --source . --no-banner --config .gitleaks.toml --redact
|
||||
./bin/lychee.exe --config .lychee.toml "docs/**/*.md" "*.md"
|
||||
```
|
||||
|
||||
Expected: gitleaks 0 leaks; lychee 0 broken (new `docs/architecture/c4-component-layers.md` + `docs/adr/ADR-005-*.md` are scanned — fix or `.lychee.toml`-exclude any link).
|
||||
|
||||
- [ ] **Step 5: Finish the branch**
|
||||
|
||||
Invoke `superpowers:finishing-a-development-branch` — present the standard options. Do **not** push without an explicit user choice. Push pattern: `git push origin feat/deptrac-architecture-fitness:main`.
|
||||
|
||||
---
|
||||
|
||||
## Self-Review
|
||||
|
||||
**1. Spec coverage (the 4 A6 gaps).** Gap «conformance / fitness» + gap «направление зависимостей / границ слоёв» → Tasks 3-5 (deptrac.yaml ruleset + baseline + lefthook job 10, red-green-proven). Gap «дрейф C4 vs код» → Task 6 (code-derived component diagram; context-level explicitly carried to a §8 checklist line — Task 7 Step 5 via the §6 paragraph / acknowledged out-of-tooling). Gap «активное проектирование архитектуры фичи» → **deliberately not a tool**: it is covered by the existing cross-cutting `brainstorming`/`writing-plans`/SPARC-architect + architecture-patterns #38, and ADR-005 (Task 3) records the layer model that design work now references — closing the gap by wiring, not by an install (per the agreed analysis; a 4th overlapping design agent would violate §5 п.6). Conflict audit: DT1→T2, DT2→T5.2, DT3→T4, DT4/DT5→T7.2, DT6→T3.1, DT7→(no task — no lint conflict), DT8→T2, DT-NUM→T1/T7.1. No gaps.
|
||||
|
||||
**2. Placeholder scan.** `#N` (Task 7) and the deptrac package version / collector syntax / formatter list (Task 3, Task 6) are **runtime-resolved by design** — Task 1 Steps 4-5 carry concrete resolution criteria (same pattern as the A6/D3/C9 plans). The `deptrac.yaml`, `lefthook.yml` job 10, and ADR-005 contents are shown in full. No "TBD" / "handle edge cases".
|
||||
|
||||
**3. Consistency.** Branch `feat/deptrac-architecture-fitness` consistent T1↔T9. Node id `deptrac` consistent T8 Steps 2-3. Category **architecture-tooling** consistent T7 Steps 2-4. Paths consistent: `app/deptrac.yaml`, `app/deptrac.baseline.yaml`, `docs/architecture/c4-component-layers.md`, `docs/adr/ADR-005-*.md`. The DT1 primary/fallback fork (composer dep vs `bin/deptrac.phar`) is flagged uniformly (T2, T5.1 `run:` line, T7.2). lefthook job count 9 → 10 consistent T5↔T9.
|
||||
|
||||
---
|
||||
|
||||
## Execution Handoff
|
||||
|
||||
Plan complete and saved to `docs/superpowers/plans/2026-05-17-deptrac-architecture-fitness-integration.md`.
|
||||
|
||||
**Sequencing decision required before execution** (see the "Sequencing" header): this epic runs **after D3**. Its slot relative to **C9** is the user's call — `… → D3 → deptrac → C9` or `… → D3 → C9 → deptrac`. Both are valid.
|
||||
|
||||
**Execution method:**
|
||||
|
||||
1. **Subagent-Driven (recommended)** — fresh subagent per task, two-stage review. *Caveat:* Task 1 Step 4-5 (WebFetch), Task 7 Step 5 (`claude-md-management`), Task 8 Step 4 (Playwright smoke) are main-session-bound — those steps stay with the controller.
|
||||
2. **Inline Execution** — `superpowers:executing-plans`, batch with checkpoints (same as the A6/D3/C9 plans — install/config/docs-heavy with interactive steps).
|
||||
@@ -0,0 +1,112 @@
|
||||
# A3 Integration-Tooling — дизайн интеграции
|
||||
|
||||
**Дата:** 2026-05-17
|
||||
**Раздел карты:** A3 «Программирование — интеграции (API, вебхуки)»
|
||||
**Параллель:** A6 architecture-tooling (17.05.2026), D3 audit-security (17.05.2026)
|
||||
**Ветка:** `feat/a3-integration-tooling` (rebased на origin/main `1313d89`, CLAUDE.md v2.8)
|
||||
|
||||
## 1. Проблема
|
||||
|
||||
Раздел A3 карты `docs/automation-graph.html` пуст — 0 узлов в `NODE_SECTION`
|
||||
(строки 1868-1911, ни одного значения `'A3'`). Интеграционная работа физически
|
||||
идёт — REST-эндпоинты (deals/lookup/billing/admin), webhook-приём
|
||||
(HMAC + per-token rate-limit), ~5 внешних API (Unisender Go / Yandex Cloud /
|
||||
Yandex 360 / JivoSite / Sentry) — но инструментами разделов A1/A5/A7/A8/E7.
|
||||
Ни один инструмент не классифицирован как A3. Состояние — как у A6 до 17.05.
|
||||
(строки 1868-1911 в оригинальном форке — после ребейза локализовать Grep'ом по `'A3'` в `NODE_SECTION`).
|
||||
|
||||
## 2. Цель и scope
|
||||
|
||||
Наполнить раздел A3 карты — параллельно A6/D3.
|
||||
|
||||
**Scope = формализация инструментов**, не их применение. A6-прецедент: поставил
|
||||
adr-kit, но не аудировал весь код; создал ADR-000/001/002 + одну C4-диаграмму как
|
||||
smoke.
|
||||
|
||||
**Вне scope:** полная OpenAPI-спека REST API проекта; рефактор webhook-кода;
|
||||
mock-инфраструктура внешних API в тестах (Microcks отклонён — требует
|
||||
Docker/JVM+Mongo, несовместим с native-Windows-no-Docker стеком проекта).
|
||||
|
||||
## 3. Состав узлов A3 (7)
|
||||
|
||||
### 3.1. Новые узлы карты (2)
|
||||
|
||||
| Узел (id карты) | Что | Установка | Tooling-реестр |
|
||||
|---|---|---|---|
|
||||
| **api-docs agent** (`ag_apidocs`, claude-flow) | генерация OpenAPI-спеки REST API, pattern learning | 0 — агент уже доступен в сессии | **нет номера** — claude-flow sub-агент; реестр Прил. Н — plugin-grain (как 11 уже существующих agent-узлов карты, ни один не имеет Tooling-номера) |
|
||||
| **openapi-mcp-server** (`mcp_openapi`, npm, stdio MCP) | отдаёт OpenAPI-спеку как MCP-ресурс/тулы; introspection своей и чужих API | `npm i` + запись в `.mcp.json` | **#47**, Tooling §4.22 |
|
||||
|
||||
### 3.2. Кросс-реф вторичным тегом A3 (5 — первично остаются в своих разделах)
|
||||
|
||||
| Узел | Первичный раздел | Роль в A3 |
|
||||
|---|---|---|
|
||||
| `context7` | E7 | актуальная дока внешних API при интеграции |
|
||||
| `mcp_boost` / Boost #10 | A1 | серверные REST-эндпоинты, HTTP-клиент, Sanctum-auth |
|
||||
| `ag_pest` / Pest 4 #18 | A5 | contract/integration-тесты эндпоинтов и вебхук-приёма |
|
||||
| `mcp_semgrep` / Semgrep #25 | A8 | безопасность интеграций (hardcoded webhook URL, API-key leak) |
|
||||
| `mcp_sentry` / Sentry MCP #34 | A7 | runtime-ошибки вебхук-хендлеров и внешних вызовов |
|
||||
|
||||
## 4. Правка модели карты (`docs/automation-graph.html`)
|
||||
|
||||
Развилка: `NODE_SECTION` строго 1:1 (узел → один раздел), кросс-реф 5
|
||||
существующих узлов в A3 механически невозможен. Решение — аддитивный слой:
|
||||
|
||||
- **Новый объект `NODE_SECTION_SECONDARY`** (`NODE_SECTION` 1:1 не трогается):
|
||||
|
||||
```js
|
||||
const NODE_SECTION_SECONDARY = {
|
||||
mcp_boost: ['A3'], context7: ['A3'], ag_pest: ['A3'],
|
||||
mcp_semgrep: ['A3'], mcp_sentry: ['A3'],
|
||||
};
|
||||
```
|
||||
|
||||
- **Цикл `SECTION_NODES`** (строка ~1915): узел добавляется в первичный раздел
|
||||
И в каждый раздел из `NODE_SECTION_SECONDARY`.
|
||||
- **Панель «Разделы»** — узел показывается под всеми своими разделами.
|
||||
- **Паспорт узла, строка «Раздел»** (`#ld-section`, строка 147): формат
|
||||
`A1 (+A3)` для кросс-реф узлов.
|
||||
- **2 новых объекта `NODES`:** `ag_apidocs` (group `agents`), `mcp_openapi`
|
||||
(group `mcp`) + позиционирование `pos()`.
|
||||
- **`NODE_SECTION`:** `ag_apidocs: 'A3', mcp_openapi: 'A3'` (первичный раздел).
|
||||
- **`NODE_TIMELINE`:** `ag_apidocs` / `mcp_openapi` — `since: '17.05.2026'`.
|
||||
- **Рёбра (~2-3):** новые узлы → governing-правила, напр.
|
||||
`E('psr_v1', 'mcp_openapi', 'R10.1 блок 3:\nintegration-tooling')`.
|
||||
- **Счётчики:** 116→118 узлов, +3 ребра. Новых конфликтов не ожидается.
|
||||
|
||||
## 5. Нормативка (4 файла — как A6/D3)
|
||||
|
||||
| Файл | Правка | Версия |
|
||||
|---|---|---|
|
||||
| Tooling Прил. Н | §4.22 (новый) — #47 openapi-mcp-server; §0 счётчик 46→47; 9-я off-phase подкатегория **integration-tooling** | v2.8→v2.9 |
|
||||
| PSR_v1 | R10.1 Блок 3 (MCP-серверы) +1 строка openapi-mcp; integration-tooling — не UI → вне R6/R14 | v3.8→v3.9 |
|
||||
| Pravila | §13.2 +абзац «Off-phase integration-tooling» | v1.22→v1.23 |
|
||||
| CLAUDE.md | §3 title 46→47; §3.3 +строка #47 (+упоминание api-docs agent); §1 row 2b 46→47; §3.3 footer; §0 cross-refs; §6 +абзац A3 | v2.8→v2.9 |
|
||||
|
||||
CLAUDE.md — через `/claude-md-management:claude-md-improver` (§5 п.10). Кросс-реф
|
||||
5 существующих инструментов — **только map-модель**; в нормативный реестр не
|
||||
попадает (инструменты уже зарегистрированы по своей идентичности).
|
||||
|
||||
## 6. Smoke / верификация
|
||||
|
||||
- Аудит установки openapi-mcp: точное имя npm-пакета (кандидат
|
||||
`ivo-toby/mcp-openapi-server`), native-Windows совместимость, stdio-режим
|
||||
(без port-conflict), кириллица в пути (квирк #26).
|
||||
- Smoke: openapi-mcp поднят на тестовой спеке (PONG-эквивалент) + dispatch
|
||||
api-docs agent на одну группу эндпоинтов (deals API) → стартовый
|
||||
OpenAPI-скелет как proof. Полную спеку не генерируем.
|
||||
- Регрессия `quick` (lint/format/type-check) перед коммитом нормативки.
|
||||
- Визуальный smoke карты: открыть `automation-graph.html` — 118 узлов,
|
||||
0 JS-ошибок, панель «Разделы» показывает A3 с 7 узлами.
|
||||
- `git` без bypass хуков.
|
||||
|
||||
## 7. Нумерация (риск реализовался — закрыт)
|
||||
|
||||
Ветка `feat/a3-integration-tooling` исходно форкнулась от D3-эры (`7c12b74`). За это время в origin/main влиты C9 (#41 CCPM / #42 product-management), deptrac (#43), A4 (#44/#45/#46). Риск из исходной редакции §7 материализовался. Закрыт ребейзом feat/a3 на актуальный origin/main `1313d89`: openapi-mcp-server подтверждён как **#47**, Tooling **§4.22**, integration-tooling — **9-я** off-phase подкатегория. Остаточный риск: если ещё одна интеграция (напр. A11) смёрджится в main раньше A3 — финально сверить счётчик Tooling §0 перед коммитом нормативки (план Task 10 Step 2).
|
||||
|
||||
## 8. Ветка и артефакты
|
||||
|
||||
- Ветка `feat/a3-integration-tooling` rebased на origin/main `1313d89`.
|
||||
- Spec: этот файл.
|
||||
- План: `docs/superpowers/plans/2026-05-17-a3-integration-tooling-integration.md`.
|
||||
- Merge: `git push origin feat/a3-integration-tooling:main` после D3 (ветка A3
|
||||
содержит D3-коммиты как предков — корректный порядок merge).
|
||||
+20
-2
@@ -31,11 +31,12 @@ pre-commit:
|
||||
# 2. markdownlint — стиль Markdown с авто-fix
|
||||
- name: markdownlint
|
||||
glob: "*.md"
|
||||
# Вендоренные сторонние скилы .claude/skills/{mermaid,ccpm}/ — не линтуем (их .md
|
||||
# Вендоренные сторонние скилы .claude/skills/{mermaid,ccpm,data-scientist}/ — не линтуем (их .md
|
||||
# не наши; markdownlint-cli2 игнорирует .markdownlintignore при явных путях).
|
||||
exclude:
|
||||
- ".claude/skills/mermaid/**"
|
||||
- ".claude/skills/ccpm/**"
|
||||
- ".claude/skills/data-scientist/**"
|
||||
run: npx markdownlint-cli2 --fix {staged_files}
|
||||
stage_fixed: true
|
||||
fail_text: |
|
||||
@@ -47,10 +48,11 @@ pre-commit:
|
||||
# (useGitignore:true) игнорирует worktree-коммиты под gitignored .claude/worktrees/.
|
||||
- name: cspell
|
||||
glob: "*.md"
|
||||
# Вендоренные сторонние скилы .claude/skills/{mermaid,ccpm}/ — не проверяем орфографию.
|
||||
# Вендоренные сторонние скилы .claude/skills/{mermaid,ccpm,data-scientist}/ — не проверяем орфографию.
|
||||
exclude:
|
||||
- ".claude/skills/mermaid/**"
|
||||
- ".claude/skills/ccpm/**"
|
||||
- ".claude/skills/data-scientist/**"
|
||||
run: npx cspell --no-progress --no-summary --no-gitignore {staged_files}
|
||||
fail_text: |
|
||||
cspell нашёл слова, отсутствующие в словаре.
|
||||
@@ -132,6 +134,22 @@ pre-commit:
|
||||
решение (ADR). Смотри file:line выше и docs/adr/ADR-*.md.
|
||||
Если ADR устарел — сначала обнови ADR (и его Enforcement-блок).
|
||||
|
||||
# 10. deptrac — архитектурный гейт направления зависимостей / границ слоёв
|
||||
# (Прил. Н #43, architecture-tooling). Анализирует app/app/** по 13 слоям
|
||||
# из app/deptrac.yaml. Первый прогон: 0 нарушений — кодовая база уже
|
||||
# конформна, baseline-файл не нужен; job падает на НОВОМ дрейфе (Model→
|
||||
# Service, Service→Http и т.п.). Без glob точечного анализа: deptrac строит
|
||||
# граф классов, нужен весь app/. Чистый PHP, без LLM (AK6).
|
||||
- name: deptrac
|
||||
glob: "app/**/*.php"
|
||||
root: "app/"
|
||||
run: php vendor/bin/deptrac analyse --no-progress
|
||||
fail_text: |
|
||||
deptrac: staged-изменение нарушает направление зависимостей между
|
||||
слоями (см. вывод выше и app/deptrac.yaml ruleset).
|
||||
Если это осознанное архитектурное изменение — сначала обнови
|
||||
app/deptrac.yaml (и при необходимости ADR-005), затем коммить.
|
||||
|
||||
# Pre-push: проверки перед git push (медленнее, но реже запускаются)
|
||||
pre-push:
|
||||
parallel: false
|
||||
|
||||
Generated
+10927
-3
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -15,7 +15,8 @@
|
||||
"a11y": "pa11y-ci --config pa11y.config.json",
|
||||
"a11y:handoff": "pa11y-ci --config pa11y-handoff.config.json",
|
||||
"check:docs": "run-p lint:md spell links a11y",
|
||||
"sast": "semgrep --config=p/php --config=p/javascript --config=p/typescript --config=p/secrets --config=.semgrep.yml --error --time"
|
||||
"sast": "semgrep --config=p/php --config=p/javascript --config=p/typescript --config=p/secrets --config=.semgrep.yml --error --time",
|
||||
"eval:llm": "promptfoo eval -c docs/ml/promptfoo-example/promptfooconfig.yaml"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cspell/dict-en_us": "^4.4.33",
|
||||
@@ -27,6 +28,7 @@
|
||||
"pa11y": "^9.1.1",
|
||||
"pa11y-ci": "^4.1.0",
|
||||
"postcss-html": "^1.8.1",
|
||||
"promptfoo": "^0.121.11",
|
||||
"stylelint": "^17.11.0",
|
||||
"stylelint-config-standard": "^40.0.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user