viberadar 0.3.140 → 0.3.141

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,69 @@ 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
+ }
1502
1565
  function buildActualizeDocsPrompt(feat, modules, currentDoc, nextVersion, changedFiles, screenshotsCaptured = false) {
1503
1566
  const sourceFiles = modules
1504
1567
  .filter(m => m.featureKeys.includes(feat.key) && m.type !== 'test' && !m.isInfra)
@@ -2219,6 +2282,46 @@ function startServer({ data: initialData, port, projectRoot }) {
2219
2282
  }
2220
2283
  prompt = buildActualizeDocsPrompt(feat, currentData.modules, currentDoc, nextVersion, changedFiles, screenshotsCaptured);
2221
2284
  }
2285
+ else if (task === 'actualize-scenario') {
2286
+ const scenarioKey = featureKey; // reuse featureKey param as scenarioKey
2287
+ if (!scenarioKey || !currentData.scenarios) {
2288
+ failBeforeStart('Сценарий не найден');
2289
+ return;
2290
+ }
2291
+ const scenarioStatus = currentData.scenarios.scenarios.find((s) => s.key === scenarioKey);
2292
+ if (!scenarioStatus) {
2293
+ failBeforeStart(`Сценарий ${scenarioKey} не найден`);
2294
+ return;
2295
+ }
2296
+ const latestVer = scenarioStatus.latestVersion ?? null;
2297
+ const nextVer = (latestVer ?? 0) + 1;
2298
+ let currentDoc = null;
2299
+ if (latestVer !== null) {
2300
+ try {
2301
+ currentDoc = fs.readFileSync(path.join(projectRoot, 'docs', 'scenarios', scenarioKey, `v${latestVer}.md`), 'utf-8');
2302
+ }
2303
+ catch { }
2304
+ }
2305
+ // Read latest docs for each referenced feature
2306
+ const featureDocs = [];
2307
+ for (const fk of scenarioStatus.featureKeys) {
2308
+ const fLabel = scenarioStatus.featureLabels[scenarioStatus.featureKeys.indexOf(fk)] || fk;
2309
+ const fDocDir = path.join(projectRoot, 'docs', 'features', fk);
2310
+ try {
2311
+ const entries = fs.readdirSync(fDocDir);
2312
+ const versions = entries
2313
+ .map((e) => { const m = e.match(/^v(\d+)\.md$/); return m ? { file: e, n: parseInt(m[1], 10) } : null; })
2314
+ .filter((x) => x !== null)
2315
+ .sort((a, b) => b.n - a.n);
2316
+ if (versions.length) {
2317
+ const content = fs.readFileSync(path.join(fDocDir, versions[0].file), 'utf-8');
2318
+ featureDocs.push({ key: fk, label: fLabel, content });
2319
+ }
2320
+ }
2321
+ catch { }
2322
+ }
2323
+ prompt = buildScenarioPrompt(scenarioStatus, featureDocs, currentDoc, nextVer);
2324
+ }
2222
2325
  else if (task === 'generate-pipelines') {
2223
2326
  if (!featureKey || !currentData.features) {
2224
2327
  failBeforeStart('Фича не найдена');
@@ -3551,6 +3654,32 @@ function startServer({ data: initialData, port, projectRoot }) {
3551
3654
  return;
3552
3655
  }
3553
3656
  // ── Documentation API ─────────────────────────────────────────────────────
3657
+ if (url === '/api/scenarios/content' && req.method === 'GET') {
3658
+ const scenarioKey = parsedUrl.searchParams.get('scenario');
3659
+ if (!scenarioKey) {
3660
+ res.writeHead(400, jsonH);
3661
+ res.end(JSON.stringify({ error: 'Missing scenario param' }));
3662
+ return;
3663
+ }
3664
+ const docDir = path.join(projectRoot, 'docs', 'scenarios', scenarioKey);
3665
+ try {
3666
+ const entries = fs.readdirSync(docDir);
3667
+ const versions = entries
3668
+ .map(e => { const m = e.match(/^v(\d+)\.md$/); return m ? { file: e, n: parseInt(m[1], 10) } : null; })
3669
+ .filter((x) => x !== null)
3670
+ .sort((a, b) => b.n - a.n);
3671
+ if (versions.length === 0)
3672
+ throw new Error('no versions');
3673
+ const content = fs.readFileSync(path.join(docDir, versions[0].file), 'utf-8');
3674
+ res.writeHead(200, jsonH);
3675
+ res.end(JSON.stringify({ content, exists: true, version: versions[0].n }));
3676
+ }
3677
+ catch {
3678
+ res.writeHead(200, jsonH);
3679
+ res.end(JSON.stringify({ content: null, exists: false }));
3680
+ }
3681
+ return;
3682
+ }
3554
3683
  if (url === '/api/docs/content' && req.method === 'GET') {
3555
3684
  const featureKey = parsedUrl.searchParams.get('feature');
3556
3685
  if (!featureKey) {
@@ -3655,6 +3784,25 @@ function startServer({ data: initialData, port, projectRoot }) {
3655
3784
  if (url.startsWith('/api/docs/export/md') && req.method === 'GET') {
3656
3785
  try {
3657
3786
  const featureKey = parsedUrl.searchParams.get('feature');
3787
+ const scenarioKey = parsedUrl.searchParams.get('scenario');
3788
+ 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, ''); };
3789
+ // Scenario export
3790
+ if (scenarioKey) {
3791
+ const scenario = currentData.scenarios?.scenarios.find((s) => s.key === scenarioKey);
3792
+ if (!scenario?.docExists) {
3793
+ res.writeHead(404, jsonH);
3794
+ res.end(JSON.stringify({ error: 'Scenario doc not found' }));
3795
+ return;
3796
+ }
3797
+ const docDir = path.join(projectRoot, 'docs', 'scenarios', scenarioKey);
3798
+ const entries = fs.readdirSync(docDir);
3799
+ 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);
3800
+ const content = fs.readFileSync(path.join(docDir, versions[0].file), 'utf-8');
3801
+ const buf = Buffer.from(content, 'utf-8');
3802
+ 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 });
3803
+ res.end(buf);
3804
+ return;
3805
+ }
3658
3806
  const docReport = currentData.documentation;
3659
3807
  if (!docReport) {
3660
3808
  res.writeHead(400, jsonH);
@@ -3694,7 +3842,6 @@ function startServer({ data: initialData, port, projectRoot }) {
3694
3842
  catch {
3695
3843
  return 'docs';
3696
3844
  } })();
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
3845
  const filename = featureKey && features.length === 1
3699
3846
  ? `${translit(features[0].label)}-v${features[0].latestVersion || 1}.md`
3700
3847
  : `${projName}-docs.md`;
@@ -3716,6 +3863,22 @@ function startServer({ data: initialData, port, projectRoot }) {
3716
3863
  if (url.startsWith('/api/docs/export/docx') && req.method === 'GET') {
3717
3864
  try {
3718
3865
  const featureKey = parsedUrl.searchParams.get('feature');
3866
+ const scenarioKeyDocx = parsedUrl.searchParams.get('scenario');
3867
+ 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, ''); };
3868
+ // Scenario DOCX export
3869
+ if (scenarioKeyDocx) {
3870
+ const scenario = currentData.scenarios?.scenarios.find((s) => s.key === scenarioKeyDocx);
3871
+ if (!scenario?.docExists) {
3872
+ res.writeHead(404, jsonH);
3873
+ res.end(JSON.stringify({ error: 'Scenario doc not found' }));
3874
+ return;
3875
+ }
3876
+ const docxBuf = (0, docx_1.buildDocx)([{ key: scenarioKeyDocx, label: scenario.label, latestVersion: scenario.latestVersion, docVersions: scenario.docVersions }], path.join(projectRoot, 'docs', 'scenarios'));
3877
+ const filename2 = `${translit2(scenario.label)}-scenario-v${scenario.latestVersion}.docx`;
3878
+ res.writeHead(200, { 'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'Content-Disposition': `attachment; filename="${filename2}"`, 'Content-Length': docxBuf.length });
3879
+ res.end(docxBuf);
3880
+ return;
3881
+ }
3719
3882
  const docReport = currentData.documentation;
3720
3883
  if (!docReport) {
3721
3884
  res.writeHead(400, jsonH);
@@ -3737,9 +3900,8 @@ function startServer({ data: initialData, port, projectRoot }) {
3737
3900
  catch {
3738
3901
  return 'docs';
3739
3902
  } })();
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
3903
  const filename = featureKey && features.length === 1
3742
- ? `${translit(features[0].label)}-v${features[0].latestVersion || 1}.docx`
3904
+ ? `${translit2(features[0].label)}-v${features[0].latestVersion || 1}.docx`
3743
3905
  : `${projName}-docs.docx`;
3744
3906
  res.writeHead(200, {
3745
3907
  'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',