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.
- package/dist/scanner/index.d.ts +27 -0
- package/dist/scanner/index.d.ts.map +1 -1
- package/dist/scanner/index.js +53 -0
- package/dist/scanner/index.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +224 -3
- package/dist/server/index.js.map +1 -1
- package/dist/ui/dashboard.html +216 -0
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -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
|
-
? `${
|
|
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',
|