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.
@@ -1499,6 +1499,107 @@ function buildScreenshotInstructions(featureKey, routes) {
1499
1499
  `- Под каждым скриншотом — курсивная подпись: *Экран входа в систему*`,
1500
1500
  ].join('\n');
1501
1501
  }
1502
+ function buildScenarioPrompt(scenario, featureDocs, currentDoc, nextVersion) {
1503
+ const outPath = `docs/scenarios/${scenario.key}/v${nextVersion}.md`;
1504
+ const isFirstVersion = nextVersion === 1;
1505
+ const featureDocBlocks = featureDocs.map(fd => `### Документация фичи "${fd.label}" (${fd.key}):\n${fd.content.slice(0, 3000)}${fd.content.length > 3000 ? '\n...(обрезано)' : ''}`).join('\n\n');
1506
+ if (isFirstVersion) {
1507
+ return [
1508
+ `Напиши пользовательский сценарий "${scenario.label}".`,
1509
+ ``,
1510
+ scenario.description ? `Цель сценария: ${scenario.description}` : '',
1511
+ ``,
1512
+ `Сценарий охватывает следующие фичи (в порядке шагов): ${scenario.featureKeys.join(' → ')}`,
1513
+ ``,
1514
+ `Ниже — документация по каждой задействованной фиче. Используй её как источник информации:`,
1515
+ ``,
1516
+ featureDocBlocks,
1517
+ ``,
1518
+ `Задача:`,
1519
+ `1. Напиши единый пошаговый сценарий от лица пользователя`,
1520
+ `2. Каждый шаг должен быть конкретным действием: что открыть, что нажать, что ввести`,
1521
+ `3. Переходы между фичами должны быть плавными — пользователь не видит "фичей", он видит задачу`,
1522
+ ``,
1523
+ `Структура документа:`,
1524
+ `# ${scenario.label}`,
1525
+ `> одна строка — какую задачу решает пользователь`,
1526
+ ``,
1527
+ `## Что понадобится`,
1528
+ `- список предусловий (что должно быть настроено/готово заранее)`,
1529
+ ``,
1530
+ `## Шаги`,
1531
+ `### Шаг 1. [Название действия]`,
1532
+ `(описание + что пользователь видит в итоге)`,
1533
+ `### Шаг 2. ...`,
1534
+ `(и т.д.)`,
1535
+ ``,
1536
+ `## Результат`,
1537
+ `Что пользователь получил в итоге всего сценария.`,
1538
+ ``,
1539
+ `## Возможные проблемы`,
1540
+ `Таблица | Проблема | Решение |`,
1541
+ ``,
1542
+ `Требования:`,
1543
+ `- Простой язык без технических терминов`,
1544
+ `- Не упоминать компоненты, файлы, API`,
1545
+ `- Описывать только то, что видит пользователь`,
1546
+ `- Запиши результат в: ${outPath}`,
1547
+ `- Создай директорию docs/scenarios/${scenario.key}/ если её нет`,
1548
+ ].filter(Boolean).join('\n');
1549
+ }
1550
+ return [
1551
+ `Актуализируй пользовательский сценарий "${scenario.label}".`,
1552
+ ``,
1553
+ `Текущая версия документа (v${nextVersion - 1}):`,
1554
+ `${currentDoc?.slice(0, 2000) || '(пусто)'}`,
1555
+ ``,
1556
+ `Актуальная документация по фичам сценария:`,
1557
+ ``,
1558
+ featureDocBlocks,
1559
+ ``,
1560
+ `Задача: обнови сценарий с учётом изменений в фичах. Сохрани структуру и стиль.`,
1561
+ `Запиши результат в: ${outPath}`,
1562
+ `Создай директорию docs/scenarios/${scenario.key}/ если её нет`,
1563
+ ].filter(Boolean).join('\n');
1564
+ }
1565
+ function buildGenerateScenariosPrompt(features, configPath, existingConfig) {
1566
+ const featureList = features.map(f => `- ${f.key}: "${f.label}"${f.description ? ` — ${f.description}` : ''}`).join('\n');
1567
+ return [
1568
+ `Проанализируй список фич продукта и сгенерируй 15 реалистичных пользовательских сценариев (user journeys).`,
1569
+ ``,
1570
+ `Фичи продукта:`,
1571
+ featureList,
1572
+ ``,
1573
+ `Требования к сценариям:`,
1574
+ `1. Каждый сценарий описывает реальный путь пользователя — от цели к результату`,
1575
+ `2. Каждый сценарий задействует 2-4 фичи из списка выше`,
1576
+ `3. Ключи сценариев — латиница через дефис, отражают суть: "first-login", "export-report"`,
1577
+ `4. Сценарии охватывают разные типы пользователей и задачи (онбординг, повседневные задачи, edge-cases)`,
1578
+ `5. НЕ повторяй фичи/темы — каждый сценарий уникален`,
1579
+ ``,
1580
+ `Запиши сценарии в файл ${configPath}, добавив секцию "scenarios" к существующему конфигу.`,
1581
+ ``,
1582
+ `Существующий конфиг:`,
1583
+ existingConfig || '{}',
1584
+ ``,
1585
+ `Формат секции "scenarios":`,
1586
+ `{`,
1587
+ ` "scenarios": {`,
1588
+ ` "ключ-сценария": {`,
1589
+ ` "label": "Человеческое название (на русском)",`,
1590
+ ` "description": "Одно предложение — что пользователь хочет достичь",`,
1591
+ ` "features": ["feature-key-1", "feature-key-2"]`,
1592
+ ` }`,
1593
+ ` }`,
1594
+ `}`,
1595
+ ``,
1596
+ `Важно:`,
1597
+ `- Используй ТОЛЬКО ключи фич из списка выше для поля "features"`,
1598
+ `- Не ломай существующий конфиг — добавь/замени только секцию "scenarios"`,
1599
+ `- Ровно 15 сценариев`,
1600
+ `- label на русском языке, description на русском`,
1601
+ ].join('\n');
1602
+ }
1502
1603
  function buildActualizeDocsPrompt(feat, modules, currentDoc, nextVersion, changedFiles, screenshotsCaptured = false) {
1503
1604
  const sourceFiles = modules
1504
1605
  .filter(m => m.featureKeys.includes(feat.key) && m.type !== 'test' && !m.isInfra)
@@ -2219,6 +2320,56 @@ function startServer({ data: initialData, port, projectRoot }) {
2219
2320
  }
2220
2321
  prompt = buildActualizeDocsPrompt(feat, currentData.modules, currentDoc, nextVersion, changedFiles, screenshotsCaptured);
2221
2322
  }
2323
+ else if (task === 'actualize-scenario') {
2324
+ const scenarioKey = featureKey; // reuse featureKey param as scenarioKey
2325
+ if (!scenarioKey || !currentData.scenarios) {
2326
+ failBeforeStart('Сценарий не найден');
2327
+ return;
2328
+ }
2329
+ const scenarioStatus = currentData.scenarios.scenarios.find((s) => s.key === scenarioKey);
2330
+ if (!scenarioStatus) {
2331
+ failBeforeStart(`Сценарий ${scenarioKey} не найден`);
2332
+ return;
2333
+ }
2334
+ const latestVer = scenarioStatus.latestVersion ?? null;
2335
+ const nextVer = (latestVer ?? 0) + 1;
2336
+ let currentDoc = null;
2337
+ if (latestVer !== null) {
2338
+ try {
2339
+ currentDoc = fs.readFileSync(path.join(projectRoot, 'docs', 'scenarios', scenarioKey, `v${latestVer}.md`), 'utf-8');
2340
+ }
2341
+ catch { }
2342
+ }
2343
+ // Read latest docs for each referenced feature
2344
+ const featureDocs = [];
2345
+ for (const fk of scenarioStatus.featureKeys) {
2346
+ const fLabel = scenarioStatus.featureLabels[scenarioStatus.featureKeys.indexOf(fk)] || fk;
2347
+ const fDocDir = path.join(projectRoot, 'docs', 'features', fk);
2348
+ try {
2349
+ const entries = fs.readdirSync(fDocDir);
2350
+ const versions = entries
2351
+ .map((e) => { const m = e.match(/^v(\d+)\.md$/); return m ? { file: e, n: parseInt(m[1], 10) } : null; })
2352
+ .filter((x) => x !== null)
2353
+ .sort((a, b) => b.n - a.n);
2354
+ if (versions.length) {
2355
+ const content = fs.readFileSync(path.join(fDocDir, versions[0].file), 'utf-8');
2356
+ featureDocs.push({ key: fk, label: fLabel, content });
2357
+ }
2358
+ }
2359
+ catch { }
2360
+ }
2361
+ prompt = buildScenarioPrompt(scenarioStatus, featureDocs, currentDoc, nextVer);
2362
+ }
2363
+ else if (task === 'generate-scenarios') {
2364
+ const configFilePath = path.join(projectRoot, 'viberadar.config.json');
2365
+ let existingConfig = null;
2366
+ try {
2367
+ existingConfig = fs.readFileSync(configFilePath, 'utf-8');
2368
+ }
2369
+ catch { }
2370
+ const feats = (currentData.features || []).map((f) => ({ key: f.key, label: f.label, description: f.description }));
2371
+ prompt = buildGenerateScenariosPrompt(feats, 'viberadar.config.json', existingConfig);
2372
+ }
2222
2373
  else if (task === 'generate-pipelines') {
2223
2374
  if (!featureKey || !currentData.features) {
2224
2375
  failBeforeStart('Фича не найдена');
@@ -2790,6 +2941,17 @@ function startServer({ data: initialData, port, projectRoot }) {
2790
2941
  const label = meta?.fieldName ? `поле ${meta.fieldName}` : meta?.recommendationType || 'логи';
2791
2942
  title = `${agentLabel} — ${label} (${count} модулей)`;
2792
2943
  }
2944
+ else if (task === 'actualize-docs') {
2945
+ const feat = currentData.features?.find(f => f.key === featureKey);
2946
+ title = feat ? `${agentLabel} — документация "${feat.label}"` : `${agentLabel} — документация`;
2947
+ }
2948
+ else if (task === 'actualize-scenario') {
2949
+ const sc = currentData.scenarios?.scenarios.find((s) => s.key === featureKey);
2950
+ title = sc ? `${agentLabel} — сценарий "${sc.label}"` : `${agentLabel} — сценарий`;
2951
+ }
2952
+ else if (task === 'generate-scenarios') {
2953
+ title = `${agentLabel} — генерация 15 сценариев`;
2954
+ }
2793
2955
  else if (task === 'generate-pipelines') {
2794
2956
  const feat = currentData.features?.find(f => f.key === featureKey);
2795
2957
  if (!feat) {
@@ -3551,6 +3713,32 @@ function startServer({ data: initialData, port, projectRoot }) {
3551
3713
  return;
3552
3714
  }
3553
3715
  // ── Documentation API ─────────────────────────────────────────────────────
3716
+ if (url === '/api/scenarios/content' && req.method === 'GET') {
3717
+ const scenarioKey = parsedUrl.searchParams.get('scenario');
3718
+ if (!scenarioKey) {
3719
+ res.writeHead(400, jsonH);
3720
+ res.end(JSON.stringify({ error: 'Missing scenario param' }));
3721
+ return;
3722
+ }
3723
+ const docDir = path.join(projectRoot, 'docs', 'scenarios', scenarioKey);
3724
+ try {
3725
+ const entries = fs.readdirSync(docDir);
3726
+ const versions = entries
3727
+ .map(e => { const m = e.match(/^v(\d+)\.md$/); return m ? { file: e, n: parseInt(m[1], 10) } : null; })
3728
+ .filter((x) => x !== null)
3729
+ .sort((a, b) => b.n - a.n);
3730
+ if (versions.length === 0)
3731
+ throw new Error('no versions');
3732
+ const content = fs.readFileSync(path.join(docDir, versions[0].file), 'utf-8');
3733
+ res.writeHead(200, jsonH);
3734
+ res.end(JSON.stringify({ content, exists: true, version: versions[0].n }));
3735
+ }
3736
+ catch {
3737
+ res.writeHead(200, jsonH);
3738
+ res.end(JSON.stringify({ content: null, exists: false }));
3739
+ }
3740
+ return;
3741
+ }
3554
3742
  if (url === '/api/docs/content' && req.method === 'GET') {
3555
3743
  const featureKey = parsedUrl.searchParams.get('feature');
3556
3744
  if (!featureKey) {
@@ -3655,6 +3843,25 @@ function startServer({ data: initialData, port, projectRoot }) {
3655
3843
  if (url.startsWith('/api/docs/export/md') && req.method === 'GET') {
3656
3844
  try {
3657
3845
  const featureKey = parsedUrl.searchParams.get('feature');
3846
+ const scenarioKey = parsedUrl.searchParams.get('scenario');
3847
+ const translit = (s) => { const m = { а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'yo', ж: 'zh', з: 'z', и: 'i', й: 'j', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't', у: 'u', ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'sch', ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu', я: 'ya' }; return s.toLowerCase().split('').map(c => m[c] ?? c).join('').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); };
3848
+ // Scenario export
3849
+ if (scenarioKey) {
3850
+ const scenario = currentData.scenarios?.scenarios.find((s) => s.key === scenarioKey);
3851
+ if (!scenario?.docExists) {
3852
+ res.writeHead(404, jsonH);
3853
+ res.end(JSON.stringify({ error: 'Scenario doc not found' }));
3854
+ return;
3855
+ }
3856
+ const docDir = path.join(projectRoot, 'docs', 'scenarios', scenarioKey);
3857
+ const entries = fs.readdirSync(docDir);
3858
+ const versions = entries.map((e) => { const mv = e.match(/^v(\d+)\.md$/); return mv ? { file: e, n: parseInt(mv[1], 10) } : null; }).filter((x) => x !== null).sort((a, b) => b.n - a.n);
3859
+ const content = fs.readFileSync(path.join(docDir, versions[0].file), 'utf-8');
3860
+ const buf = Buffer.from(content, 'utf-8');
3861
+ res.writeHead(200, { 'Content-Type': 'text/markdown; charset=utf-8', 'Content-Disposition': `attachment; filename="${translit(scenario.label)}-scenario-v${scenario.latestVersion}.md"`, 'Content-Length': buf.length });
3862
+ res.end(buf);
3863
+ return;
3864
+ }
3658
3865
  const docReport = currentData.documentation;
3659
3866
  if (!docReport) {
3660
3867
  res.writeHead(400, jsonH);
@@ -3694,7 +3901,6 @@ function startServer({ data: initialData, port, projectRoot }) {
3694
3901
  catch {
3695
3902
  return 'docs';
3696
3903
  } })();
3697
- const translit = (s) => { const m = { а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'yo', ж: 'zh', з: 'z', и: 'i', й: 'j', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't', у: 'u', ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'sch', ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu', я: 'ya' }; return s.toLowerCase().split('').map(c => m[c] ?? c).join('').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); };
3698
3904
  const filename = featureKey && features.length === 1
3699
3905
  ? `${translit(features[0].label)}-v${features[0].latestVersion || 1}.md`
3700
3906
  : `${projName}-docs.md`;
@@ -3716,6 +3922,22 @@ function startServer({ data: initialData, port, projectRoot }) {
3716
3922
  if (url.startsWith('/api/docs/export/docx') && req.method === 'GET') {
3717
3923
  try {
3718
3924
  const featureKey = parsedUrl.searchParams.get('feature');
3925
+ const scenarioKeyDocx = parsedUrl.searchParams.get('scenario');
3926
+ const translit2 = (s) => { const m = { а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'yo', ж: 'zh', з: 'z', и: 'i', й: 'j', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't', у: 'u', ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'sch', ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu', я: 'ya' }; return s.toLowerCase().split('').map(c => m[c] ?? c).join('').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); };
3927
+ // Scenario DOCX export
3928
+ if (scenarioKeyDocx) {
3929
+ const scenario = currentData.scenarios?.scenarios.find((s) => s.key === scenarioKeyDocx);
3930
+ if (!scenario?.docExists) {
3931
+ res.writeHead(404, jsonH);
3932
+ res.end(JSON.stringify({ error: 'Scenario doc not found' }));
3933
+ return;
3934
+ }
3935
+ const docxBuf = (0, docx_1.buildDocx)([{ key: scenarioKeyDocx, label: scenario.label, latestVersion: scenario.latestVersion, docVersions: scenario.docVersions }], path.join(projectRoot, 'docs', 'scenarios'));
3936
+ const filename2 = `${translit2(scenario.label)}-scenario-v${scenario.latestVersion}.docx`;
3937
+ res.writeHead(200, { 'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'Content-Disposition': `attachment; filename="${filename2}"`, 'Content-Length': docxBuf.length });
3938
+ res.end(docxBuf);
3939
+ return;
3940
+ }
3719
3941
  const docReport = currentData.documentation;
3720
3942
  if (!docReport) {
3721
3943
  res.writeHead(400, jsonH);
@@ -3737,9 +3959,8 @@ function startServer({ data: initialData, port, projectRoot }) {
3737
3959
  catch {
3738
3960
  return 'docs';
3739
3961
  } })();
3740
- const translit = (s) => { const m = { а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'yo', ж: 'zh', з: 'z', и: 'i', й: 'j', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't', у: 'u', ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'sch', ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu', я: 'ya' }; return s.toLowerCase().split('').map(c => m[c] ?? c).join('').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); };
3741
3962
  const filename = featureKey && features.length === 1
3742
- ? `${translit(features[0].label)}-v${features[0].latestVersion || 1}.docx`
3963
+ ? `${translit2(features[0].label)}-v${features[0].latestVersion || 1}.docx`
3743
3964
  : `${projName}-docs.docx`;
3744
3965
  res.writeHead(200, {
3745
3966
  'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',