viberadar 0.3.140 → 0.3.142

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1585,6 +1585,7 @@ const modeStore = {
1585
1585
  qa: { view: 'features', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
1586
1586
  observability: { view: 'files', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
1587
1587
  docs: { view: 'features', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
1588
+ scenarios: { view: 'list', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
1588
1589
  services: { view: 'graph', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null, svcTab: 'graph' },
1589
1590
  };
1590
1591
 
@@ -2942,6 +2943,7 @@ function renderModeSwitch() {
2942
2943
  { key: 'qa', label: 'QA Coverage', hint: 'Покрытие, пробелы, тренды' },
2943
2944
  { key: 'observability', label: 'Наблюдаемость', hint: 'Логи, шум, сигналы ошибок' },
2944
2945
  { key: 'docs', label: 'Документация', hint: 'Актуальность, генерация, обновление' },
2946
+ { key: 'scenarios', label: 'Сценарии', hint: 'Пользовательские сценарии, user journeys' },
2945
2947
  { key: 'services', label: 'Карта сервисов', hint: 'Зависимости, пайплайны, мониторинг' },
2946
2948
  ];
2947
2949
  root.innerHTML = modes.map(m => `
@@ -2981,6 +2983,20 @@ function renderSidebar() {
2981
2983
  return;
2982
2984
  }
2983
2985
 
2986
+ if (contextMode === 'scenarios') {
2987
+ tabs.style.display = 'none';
2988
+ const sr = D.scenarios;
2989
+ extra.innerHTML = `
2990
+ <div class="sidebar-label">Сценарии</div>
2991
+ <div style="font-size:12px;color:var(--muted);padding:0 6px;line-height:1.45">
2992
+ Пользовательские сценарии и user journeys.
2993
+ </div>
2994
+ ${sr ? `<div style="margin-top:12px;padding:0 6px;font-size:12px;color:var(--muted)">
2995
+ <span style="color:var(--green)">${sr.withDocCount}</span> готово &nbsp;·&nbsp; <span style="color:var(--red)">${sr.missingDocCount}</span> без доки
2996
+ </div>` : ''}`;
2997
+ return;
2998
+ }
2999
+
2984
3000
  if (contextMode === 'services') {
2985
3001
  tabs.style.display = 'none';
2986
3002
  const svcTab = modeStore.services.svcTab || 'graph';
@@ -3053,6 +3069,10 @@ function renderContent() {
3053
3069
  renderDocumentation(c);
3054
3070
  return;
3055
3071
  }
3072
+ if (contextMode === 'scenarios') {
3073
+ renderScenarios(c);
3074
+ return;
3075
+ }
3056
3076
  if (contextMode === 'services') {
3057
3077
  renderServiceMap(c);
3058
3078
  return;
@@ -4398,6 +4418,202 @@ function renderObservabilityOverview(c) {
4398
4418
 
4399
4419
  // ─── Documentation screen ─────────────────────────────────────────────────────
4400
4420
 
4421
+ // ─── Scenarios ───────────────────────────────────────────────────────────────
4422
+
4423
+ let scenarioDrillKey = null;
4424
+
4425
+ function renderScenarios(c) {
4426
+ const sr = D.scenarios;
4427
+ if (!sr || sr.totalScenarios === 0) {
4428
+ const hasFeatures = D.features && D.features.length > 0;
4429
+ c.innerHTML = `<div class="onboarding-block">
4430
+ <h3>Сценарии</h3>
4431
+ <p style="color:var(--muted);margin-bottom:16px">Пользовательские сценарии — это кросс-фичевые user journeys.<br>Агент проанализирует фичи продукта и придумает реалистичные сценарии.</p>
4432
+ ${hasFeatures ? `
4433
+ <button class="agent-card-btn" id="generateScenariosBtn" style="font-size:14px;padding:10px 20px;background:var(--blue);color:#fff;border:none;border-radius:6px;cursor:pointer">
4434
+ ✨ Сгенерировать 15 сценариев
4435
+ </button>
4436
+ <p style="font-size:11px;color:var(--muted);margin-top:10px">Агент изучит ${D.features.length} фич и предложит 15 user journeys в viberadar.config.json</p>
4437
+ ` : `<p style="color:var(--red)">Сначала добавьте фичи в viberadar.config.json</p>`}
4438
+ </div>`;
4439
+ if (hasFeatures) {
4440
+ document.getElementById('generateScenariosBtn').onclick = () => runAgentTask('generate-scenarios');
4441
+ }
4442
+ return;
4443
+ }
4444
+ if (scenarioDrillKey) renderScenarioDetail(c);
4445
+ else renderScenarioCards(c);
4446
+ }
4447
+
4448
+ function renderScenarioCards(c) {
4449
+ const sr = D.scenarios;
4450
+ if (!sr || !sr.scenarios.length) {
4451
+ c.innerHTML = `<div class="onboarding-block"><p>Сценарии не найдены в viberadar.config.json.</p></div>`;
4452
+ return;
4453
+ }
4454
+
4455
+ const missingDocs = sr.scenarios.filter(s => !s.docExists);
4456
+
4457
+ let html = `
4458
+ <div class="doc-kpi-strip">
4459
+ <div class="doc-kpi"><span class="doc-kpi-val">${sr.totalScenarios}</span><span class="doc-kpi-lbl">Сценариев</span></div>
4460
+ <div class="doc-kpi"><span class="doc-kpi-val" style="color:var(--green)">${sr.withDocCount}</span><span class="doc-kpi-lbl">Готово</span></div>
4461
+ <div class="doc-kpi"><span class="doc-kpi-val" style="color:var(--red)">${sr.missingDocCount}</span><span class="doc-kpi-lbl">Без доки</span></div>
4462
+ <div style="flex:1"></div>
4463
+ ${missingDocs.length > 0 ? `<button class="agent-card-btn" id="scenarioWriteAllBtn" style="background:var(--blue);color:#fff;border:none;border-radius:6px;padding:6px 14px;cursor:pointer;font-size:13px">
4464
+ ✍ Написать доку для всех (${missingDocs.length})
4465
+ </button>` : ''}
4466
+ <button class="agent-card-btn" id="scenarioRegenBtn" style="background:var(--card-bg);color:var(--muted);border:1px solid var(--border);border-radius:6px;padding:6px 14px;cursor:pointer;font-size:13px;margin-left:8px">
4467
+ ↻ Перегенерировать
4468
+ </button>
4469
+ </div>
4470
+ <div class="features-grid">`;
4471
+
4472
+ for (const s of sr.scenarios) {
4473
+ const hasDoc = s.docExists;
4474
+ const statusColor = hasDoc ? 'var(--green)' : 'var(--red)';
4475
+ const statusText = hasDoc ? `v${s.latestVersion}` : 'Нет доки';
4476
+ const lastUpd = s.lastUpdated ? new Date(s.lastUpdated).toLocaleDateString('ru-RU') : '—';
4477
+ html += `
4478
+ <div class="doc-feature-card" data-key="${s.key}" style="cursor:pointer">
4479
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
4480
+ <span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${s.color};flex-shrink:0"></span>
4481
+ <span style="font-weight:600;font-size:14px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escapeHtml(s.label)}</span>
4482
+ <span style="font-size:11px;color:${statusColor};font-weight:600;flex-shrink:0">${statusText}</span>
4483
+ </div>
4484
+ ${s.description ? `<div style="font-size:12px;color:var(--muted);margin-bottom:8px;line-height:1.4">${escapeHtml(s.description)}</div>` : ''}
4485
+ <div style="font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap">
4486
+ ${s.featureLabels.map(l => `<span style="background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:1px 6px">${escapeHtml(l)}</span>`).join('')}
4487
+ </div>
4488
+ ${hasDoc ? `<div style="font-size:11px;color:var(--muted);margin-top:6px">Обновлено: ${lastUpd}</div>` : ''}
4489
+ </div>`;
4490
+ }
4491
+ html += '</div>';
4492
+ c.innerHTML = html;
4493
+
4494
+ c.querySelectorAll('.doc-feature-card').forEach(card => {
4495
+ card.addEventListener('click', () => {
4496
+ scenarioDrillKey = card.dataset.key;
4497
+ renderContent();
4498
+ });
4499
+ });
4500
+
4501
+ const writeAllBtn = document.getElementById('scenarioWriteAllBtn');
4502
+ if (writeAllBtn) {
4503
+ writeAllBtn.onclick = () => {
4504
+ if (agentRunning) { alert('Агент уже запущен, задачи будут поставлены в очередь'); }
4505
+ const missing = sr.scenarios.filter(s => !s.docExists);
4506
+ missing.forEach((s, i) => {
4507
+ setTimeout(() => runAgentTask('actualize-scenario', s.key), i * 300);
4508
+ });
4509
+ };
4510
+ }
4511
+
4512
+ const regenBtn = document.getElementById('scenarioRegenBtn');
4513
+ if (regenBtn) {
4514
+ regenBtn.onclick = () => {
4515
+ if (confirm(`Перегенерировать все ${sr.totalScenarios} сценариев? Агент заново изучит фичи и перепишет viberadar.config.json`)) {
4516
+ runAgentTask('generate-scenarios');
4517
+ }
4518
+ };
4519
+ }
4520
+ }
4521
+
4522
+ async function renderScenarioDetail(c) {
4523
+ const sr = D.scenarios;
4524
+ const s = sr?.scenarios.find(x => x.key === scenarioDrillKey);
4525
+ if (!s) { scenarioDrillKey = null; renderContent(); return; }
4526
+
4527
+ const statusColor = s.docExists ? 'var(--green)' : 'var(--red)';
4528
+ const statusText = s.docExists ? `Готово · v${s.latestVersion}` : 'Нет документации';
4529
+ const lastUpd = s.lastUpdated ? new Date(s.lastUpdated).toLocaleDateString('ru-RU') : '—';
4530
+ const nextVersion = (s.latestVersion || 0) + 1;
4531
+ const btnLabel = s.docExists ? `Актуализировать сценарий → v${nextVersion}` : 'Сгенерировать сценарий → v1';
4532
+ const btnHint = s.docExists
4533
+ ? 'Агент перечитает документацию фич и обновит сценарий.'
4534
+ : 'Агент прочитает документацию связанных фич и напишет пошаговый сценарий.';
4535
+
4536
+ const versionsHtml = s.docVersions && s.docVersions.length > 0
4537
+ ? `<div style="font-size:11px;color:var(--muted);margin-top:6px">Версии: ${s.docVersions.map((v, i) => `<span style="${i === s.docVersions.length - 1 ? 'color:var(--text)' : ''}">${v.split('/').pop()}</span>`).join(' · ')}</div>`
4538
+ : '';
4539
+
4540
+ const exportButtons = s.docExists ? `
4541
+ <a href="/api/docs/export/md?scenario=${encodeURIComponent(s.key)}" download class="doc-agent-btn" style="font-size:13px;padding:6px 14px;text-decoration:none">⬇ .md</a>
4542
+ <a href="/api/docs/export/docx?scenario=${encodeURIComponent(s.key)}" download class="doc-agent-btn" style="font-size:13px;padding:6px 14px;text-decoration:none">⬇ .docx</a>` : '';
4543
+
4544
+ // Check if referenced feature docs are missing
4545
+ const missingDocs = s.featureKeys.filter((fk, i) => {
4546
+ const featDoc = D.documentation?.features.find(f => f.key === fk);
4547
+ return !featDoc?.docExists;
4548
+ });
4549
+ const warningHtml = missingDocs.length > 0
4550
+ ? `<div style="margin-top:8px;padding:8px 12px;border-radius:6px;background:rgba(210,153,34,0.1);border:1px solid rgba(210,153,34,0.3);font-size:12px;color:var(--yellow)">
4551
+ ⚠️ Документация отсутствует для: ${missingDocs.map(k => `<strong>${escapeHtml(D.documentation?.features.find(f => f.key === k)?.label || k)}</strong>`).join(', ')}. Агент сможет сгенерировать сценарий, но качество будет ниже.
4552
+ </div>` : '';
4553
+
4554
+ c.innerHTML = `
4555
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:16px;flex-wrap:wrap">
4556
+ <button class="back-btn" id="scenarioBackBtn">← Все сценарии</button>
4557
+ <span style="width:12px;height:12px;border-radius:50%;background:${s.color};display:inline-block;flex-shrink:0"></span>
4558
+ <h2 style="font-size:18px;font-weight:700;margin:0;flex:1">${escapeHtml(s.label)}</h2>
4559
+ <div style="display:flex;align-items:center;font-size:13px"><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${statusColor};margin-right:6px"></span><span style="color:${statusColor};font-weight:600">${statusText}</span></div>
4560
+ ${s.docExists ? `<span style="font-size:12px;color:var(--muted)">Обновлено: ${lastUpd}</span>` : ''}
4561
+ <div style="margin-left:auto;display:flex;gap:8px;align-items:center">
4562
+ ${exportButtons}
4563
+ </div>
4564
+ </div>
4565
+
4566
+ <div style="margin-bottom:8px;font-size:12px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap;align-items:center">
4567
+ <span>Фичи:</span>
4568
+ ${s.featureLabels.map(l => `<span style="background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:2px 8px;font-size:12px">${escapeHtml(l)}</span>`).join('')}
4569
+ </div>
4570
+
4571
+ <div class="doc-detail-actions">
4572
+ <div class="doc-action-step">
4573
+ <div>
4574
+ <div class="doc-step-label">${btnLabel}</div>
4575
+ <div class="doc-step-hint">${btnHint}</div>
4576
+ <div style="margin-top:8px">
4577
+ <button class="agent-card-btn doc-detail-agent-btn" id="scenarioActualizeBtn" data-task="actualize-scenario" data-key="${s.key}">${btnLabel}</button>
4578
+ </div>
4579
+ ${warningHtml}
4580
+ <div id="scenarioActualizeStatus" style="display:none;margin-top:10px;padding:8px 12px;border-radius:6px;background:rgba(88,166,255,0.08);border:1px solid rgba(88,166,255,0.2);font-size:12px;color:var(--blue)"></div>
4581
+ ${versionsHtml}
4582
+ </div>
4583
+ </div>
4584
+ </div>
4585
+
4586
+ <div id="scenarioContent" style="margin-top:16px;padding:24px;background:var(--bg-card);border-radius:8px;border:1px solid var(--border)">
4587
+ <div style="color:var(--muted);font-size:13px">Загрузка...</div>
4588
+ </div>`;
4589
+
4590
+ document.getElementById('scenarioBackBtn').onclick = () => { scenarioDrillKey = null; renderContent(); };
4591
+
4592
+ document.getElementById('scenarioActualizeBtn').addEventListener('click', (e) => {
4593
+ e.stopPropagation();
4594
+ runAgentTask('actualize-scenario', s.key);
4595
+ });
4596
+
4597
+ // Load content
4598
+ if (s.docExists) {
4599
+ try {
4600
+ const res = await fetch(`/api/scenarios/content?scenario=${encodeURIComponent(s.key)}`);
4601
+ const data = await res.json();
4602
+ if (data.content) {
4603
+ document.getElementById('scenarioContent').innerHTML = renderMarkdownToHtml(data.content);
4604
+ } else {
4605
+ document.getElementById('scenarioContent').innerHTML = '<div style="color:var(--muted);font-size:13px">Документация отсутствует.</div>';
4606
+ }
4607
+ } catch {
4608
+ document.getElementById('scenarioContent').innerHTML = '<div style="color:var(--red);font-size:13px">Ошибка загрузки.</div>';
4609
+ }
4610
+ } else {
4611
+ document.getElementById('scenarioContent').innerHTML = `<div style="color:var(--muted);font-size:13px;text-align:center;padding:32px 0">Нажмите «${escapeHtml(btnLabel)}» чтобы сгенерировать документацию.</div>`;
4612
+ }
4613
+ }
4614
+
4615
+ // ─────────────────────────────────────────────────────────────────────────────
4616
+
4401
4617
  function renderDocumentation(c) {
4402
4618
  const doc = D.documentation;
4403
4619
  if (!doc) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viberadar",
3
- "version": "0.3.140",
3
+ "version": "0.3.142",
4
4
  "description": "Live module map with test coverage for vibecoding projects",
5
5
  "main": "./dist/cli.js",
6
6
  "bin": {