viberadar 0.3.72 → 0.3.74

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.
@@ -187,17 +187,6 @@ function buildAgentShellCmd(agent, taskFile, codexSandboxMode, model) {
187
187
  }
188
188
  return `claude --print --verbose --output-format stream-json${modelFlag} < "${escaped}"`;
189
189
  }
190
- function buildAgentSpawnEnv(agent) {
191
- const env = { ...process.env };
192
- if (agent === 'codex') {
193
- // Prevent nested Codex launches from inheriting Desktop thread context
194
- // that can inject incompatible settings (e.g. model_reasoning_effort).
195
- delete env.CODEX_THREAD_ID;
196
- delete env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE;
197
- delete env.CODEX_SHELL;
198
- }
199
- return env;
200
- }
201
190
  /** Parse a Claude Code stream-json event into a human-readable line, or null to skip */
202
191
  function parseClaudeEvent(raw) {
203
192
  let event;
@@ -788,6 +777,325 @@ function buildMapUnmappedPrompt(modules, features) {
788
777
  ...unmapped,
789
778
  ].join('\n');
790
779
  }
780
+ // ─── Observability prompt builders ────────────────────────────────────────────
781
+ const LOGGING_STANDARD_INLINE = [
782
+ `## Стандарт логирования (инлайн)`,
783
+ `Обязательные поля для каждого структурированного лога:`,
784
+ `- timestamp (ISO-8601) — обычно ставится логгером автоматически`,
785
+ `- service — имя сервиса (billing-api, auth и т.д.)`,
786
+ `- env — среда (local|dev|stage|prod), из NODE_ENV`,
787
+ `- level — DEBUG|INFO|WARN|ERROR`,
788
+ `- trace_id — ID распределённого трейса`,
789
+ `- request_id — сквозной request-id`,
790
+ `- user_id или user_hash — идентификатор пользователя`,
791
+ `- event_name — доменное имя события, формат: <domain>.<entity>.<action> (lower_snake_case через точку)`,
792
+ `- outcome — success|failure|partial`,
793
+ `- error_code — код ошибки (для WARN/ERROR обязателен)`,
794
+ ``,
795
+ `Допустимые error_code: VALIDATION_ERROR, UNAUTHORIZED, FORBIDDEN, RESOURCE_NOT_FOUND, CONFLICT, RATE_LIMITED, DEPENDENCY_TIMEOUT, DEPENDENCY_UNAVAILABLE, DB_TIMEOUT, DB_CONSTRAINT_VIOLATION, INTERNAL_ERROR`,
796
+ ``,
797
+ `Правила лог-уровней:`,
798
+ `- DEBUG — только локальная диагностика, в prod выключен`,
799
+ `- INFO — значимые бизнес-события и lifecycle операции`,
800
+ `- WARN — деградация, ретраи, graceful fallback`,
801
+ `- ERROR — фактический сбой операции`,
802
+ ].join('\n');
803
+ const SUPPRESS_GUARD = `
804
+ ⛔ СТРОГО ЗАПРЕЩЕНО (нарушение = потеря наблюдаемости в prod):
805
+ - Трогать logger.warn / logger.error / logger.fatal — это сигналы деградации и сбоев, не шум
806
+ - Понижать WARN → INFO, WARN → DEBUG, ERROR → WARN — это критическая потеря
807
+ - Удалять или переименовывать ERROR/FATAL логи
808
+
809
+ ✅ Работать ТОЛЬКО с:
810
+ - logger.info / logger.debug / logger.trace которые неструктурированы или lifecycle-мусор
811
+ - Конкретные сообщения указаны в задаче ниже
812
+ `.trim();
813
+ function buildObsSuppressPatternPrompt(pattern, recommendation, catalog) {
814
+ const relatedModules = catalog
815
+ .filter(c => c.recommendation === 'suppress' || c.recommendation === 'downgrade level')
816
+ .map(c => {
817
+ const snippets = (c.noisyMessages || []).slice(0, 3).map(m => ` • "${m}"`).join('\n');
818
+ return `- ${c.modulePath} (format: ${c.format})\n${snippets}`;
819
+ })
820
+ .slice(0, 15);
821
+ return [
822
+ `Убери шумные лог-вызовы уровня INFO/DEBUG/TRACE.`,
823
+ ``,
824
+ SUPPRESS_GUARD,
825
+ ``,
826
+ `Конкретный паттерн для поиска: "${pattern}"`,
827
+ ``,
828
+ relatedModules.length > 0
829
+ ? `Модули где встречается шум (с примерами сообщений):\n${relatedModules.join('\n')}`
830
+ : '',
831
+ ``,
832
+ `Что сделать с каждым найденным вызовом logger.info/debug/trace, порождающим этот паттерн:`,
833
+ `- УДАЛИ полностью, если это lifecycle-мусор: "started", "done", "ok", "loaded", "ready", "ping"`,
834
+ `- СТРУКТУРИРУЙ в logger.debug({ service, event_name, outcome, ...данные }), если несёт диагностическую ценность`,
835
+ `- НЕ ТРОГАЙ, если это logger.warn / logger.error / logger.fatal — даже если сообщение похоже на шум`,
836
+ ``,
837
+ `\n${LOGGING_STANDARD_INLINE}`,
838
+ ].filter(Boolean).join('\n');
839
+ }
840
+ function buildObsAddCriticalLogsPrompt(modulePath, catalog) {
841
+ const moduleItem = catalog.find(c => c.modulePath === modulePath);
842
+ const missingFields = moduleItem?.missingFields || [];
843
+ return [
844
+ `Добавь критичные логи (warn/error) в модуль \`${modulePath}\`.`,
845
+ ``,
846
+ `Сейчас в модуле нет warn/error событий. При сбоях мы не увидим ошибку в логах.`,
847
+ ``,
848
+ moduleItem
849
+ ? `Текущее состояние модуля:\n- Формат: ${moduleItem.format}\n- Уровень: ${moduleItem.level}\n- Пропущенные поля: ${missingFields.length > 0 ? missingFields.join(', ') : 'нет'}`
850
+ : '',
851
+ ``,
852
+ `Что сделать:`,
853
+ `- Найди в модуле точки, где может произойти ошибка (catch-блоки, проверки null/undefined, HTTP-ответы с ошибкой, DB-ошибки)`,
854
+ `- Добавь logger.warn или logger.error с обязательными полями по стандарту`,
855
+ `- Каждый лог должен включать: event_name, outcome, error_code (для error)`,
856
+ `- Именование event_name: <domain>.<entity>.<action> (lower_snake_case через точку)`,
857
+ `- Используй error_code из утверждённого словаря (config/logging-error-codes.json)`,
858
+ ``,
859
+ `\n${LOGGING_STANDARD_INLINE}`,
860
+ ].filter(Boolean).join('\n');
861
+ }
862
+ const FP_TYPE_LABELS = {
863
+ 'empty-catch': 'Пустой catch-блок — проглатывает ошибку',
864
+ 'catch-no-log': 'catch без логирования ошибки',
865
+ 'promise-catch-no-log': '.catch() без логирования',
866
+ 'http-no-error-handling': 'HTTP-вызов без обработки ошибок',
867
+ 'db-no-error-handling': 'DB-операция без обработки ошибок',
868
+ 'throw-no-log': 'throw без предшествующего logger.error',
869
+ 'error-check-no-log': 'Проверка ошибки (if err) без логирования',
870
+ };
871
+ function buildObsAddCriticalLogsPromptV2(item, catalog) {
872
+ const catalogEntry = catalog.find(c => c.modulePath === item.modulePath);
873
+ const fpDescriptions = item.failurePoints.map(fp => `- Строка ~${fp.lineApprox}: ${FP_TYPE_LABELS[fp.type] || fp.type}\n \`${fp.snippet}\``).join('\n');
874
+ return [
875
+ `Добавь критичные логи (warn/error) в модуль \`${item.modulePath}\`.`,
876
+ ``,
877
+ `Роль модуля: ${item.roleHint} (приоритет: ${item.riskTier})`,
878
+ item.hasAnyWarnError
879
+ ? `В модуле есть некоторые warn/error, но обнаружены незакрытые точки отказа.`
880
+ : `В модуле НЕТ ни одного warn/error. При сбоях мы не увидим ошибку в логах.`,
881
+ ``,
882
+ catalogEntry
883
+ ? `Текущее состояние:\n- Формат: ${catalogEntry.format}\n- Уровень: ${catalogEntry.level}\n- Пропущенные поля: ${(catalogEntry.missingFields || []).join(', ') || 'нет'}`
884
+ : '',
885
+ ``,
886
+ `Обнаруженные точки отказа без логирования (${item.failurePoints.length}):`,
887
+ fpDescriptions,
888
+ ``,
889
+ `Что сделать с каждой точкой:`,
890
+ `- Пустые catch-блоки: добавь logger.error с контекстом, event_name, error_code, outcome:failure`,
891
+ `- catch без лога: добавь logger.error/warn рядом с обработкой ошибки`,
892
+ `- .catch() без лога: добавь logger.error в обработчик промиса`,
893
+ `- HTTP/DB без обработки: оберни в try/catch с logger.error`,
894
+ `- throw без лога: добавь logger.error ДО throw`,
895
+ `- if(err) без лога: добавь logger.warn/error в ветку ошибки`,
896
+ ``,
897
+ `Каждый лог: event_name, outcome, error_code (для error).`,
898
+ `event_name: <domain>.<entity>.<action> (lower_snake_case через точку)`,
899
+ ``,
900
+ `\n${LOGGING_STANDARD_INLINE}`,
901
+ ].filter(Boolean).join('\n');
902
+ }
903
+ function buildObsBatchAddCriticalLogsPrompt(items, catalog) {
904
+ const moduleBlocks = items.map(item => {
905
+ const fpSummary = item.failurePoints.slice(0, 5).map(fp => ` - строка ~${fp.lineApprox}: ${FP_TYPE_LABELS[fp.type] || fp.type} — \`${fp.snippet}\``).join('\n');
906
+ return `### \`${item.modulePath}\` (${item.roleHint}, ${item.riskTier})\n${fpSummary || ' - Нет warn/error, проверь весь модуль на точки отказа'}`;
907
+ }).join('\n\n');
908
+ return [
909
+ `Добавь критичные логи в ${items.length} модулей.`,
910
+ ``,
911
+ `Для каждого модуля: найди точки отказа (указаны ниже) и добавь logger.warn/error с обязательными полями.`,
912
+ ``,
913
+ moduleBlocks,
914
+ ``,
915
+ `Требования к каждому логу:`,
916
+ `- event_name: <domain>.<entity>.<action> (lower_snake_case через точку)`,
917
+ `- outcome: failure|partial`,
918
+ `- error_code из словаря (VALIDATION_ERROR, DEPENDENCY_TIMEOUT, INTERNAL_ERROR и т.д.)`,
919
+ `- Пустые catch: добавь logger.error, не оставляй пустыми`,
920
+ `- HTTP/DB без обработки: оберни в try/catch с logger.error`,
921
+ ``,
922
+ `\n${LOGGING_STANDARD_INLINE}`,
923
+ ].filter(Boolean).join('\n');
924
+ }
925
+ function buildObsEnrichFieldPrompt(fieldName, catalog) {
926
+ const affectedModules = catalog
927
+ .filter(c => c.missingFields.includes(fieldName))
928
+ .map(c => `- ${c.modulePath} (format: ${c.format}, missing: ${c.missingFields.join(', ')})`)
929
+ .slice(0, 30);
930
+ const fieldHints = {
931
+ service: 'Имя сервиса. Берётся из конфига или env-переменной, не хардкодится.',
932
+ env: 'Среда (local|dev|stage|prod). Берётся из NODE_ENV или конфига.',
933
+ trace_id: 'ID распределённого трейса. Передаётся через middleware из заголовка или генерируется.',
934
+ request_id: 'Сквозной ID запроса. Берётся из заголовка X-Request-Id или генерируется middleware.',
935
+ event_name: 'Доменное событие. Формат: <domain>.<entity>.<action> (lower_snake_case через точку).',
936
+ outcome: 'Результат операции: success|failure|partial.',
937
+ error_code: 'Код ошибки из словаря (config/logging-error-codes.json). Обязателен для WARN/ERROR.',
938
+ user_id: 'ID пользователя (user_id для внутренних, user_hash для внешних). Берётся из контекста auth.',
939
+ };
940
+ return [
941
+ `Добавь обязательное поле \`${fieldName}\` во все лог-вызовы, где оно отсутствует.`,
942
+ ``,
943
+ fieldHints[fieldName] ? `Описание поля: ${fieldHints[fieldName]}` : '',
944
+ ``,
945
+ affectedModules.length > 0
946
+ ? `Модули с пропущенным полем "${fieldName}" (${affectedModules.length}):\n${affectedModules.join('\n')}`
947
+ : '',
948
+ ``,
949
+ `Что сделать:`,
950
+ `- Открой каждый модуль из списка`,
951
+ `- Найди все вызовы логгера (logger.info, logger.warn, logger.error и т.д.)`,
952
+ `- Добавь поле "${fieldName}" в каждый вызов, где оно отсутствует`,
953
+ `- Значение поля должно быть взято из контекста (request, config, env) — не хардкодь`,
954
+ ``,
955
+ `\n${LOGGING_STANDARD_INLINE}`,
956
+ ].filter(Boolean).join('\n');
957
+ }
958
+ function buildObsBatchRecommendationPrompt(recommendationType, catalog) {
959
+ const isSuppressTask = recommendationType === 'suppress';
960
+ const affected = catalog
961
+ .filter(c => c.recommendation === recommendationType)
962
+ .map(c => {
963
+ const base = `- ${c.modulePath} (format: ${c.format}, level: ${c.level}, missing: ${c.missingFields.join(', ') || 'none'})`;
964
+ if (isSuppressTask && c.noisyMessages && c.noisyMessages.length > 0) {
965
+ const snippets = c.noisyMessages.slice(0, 3).map(m => ` • "${m}"`).join('\n');
966
+ return `${base}\n${snippets}`;
967
+ }
968
+ return base;
969
+ })
970
+ .slice(0, 30);
971
+ const actionMap = {
972
+ 'suppress': 'Удали или структурируй шумные INFO/DEBUG/TRACE логи (WARN/ERROR не трогать!)',
973
+ 'downgrade level': 'Понизь уровень только INFO-логов без ценности → debug. WARN/ERROR не трогать.',
974
+ 'enrich fields': 'Добавь недостающие обязательные поля в лог-вызовы каждого модуля',
975
+ 'add event': 'Добавь warn/error события в модули, где их нет',
976
+ };
977
+ return [
978
+ `Batch-исправление: ${recommendationType} для ${affected.length} модулей.`,
979
+ ``,
980
+ isSuppressTask ? SUPPRESS_GUARD : '',
981
+ ``,
982
+ `Действие: ${actionMap[recommendationType] || recommendationType}`,
983
+ ``,
984
+ `Модули (с примерами шумных сообщений):\n${affected.join('\n')}`,
985
+ ``,
986
+ `Требования:`,
987
+ `- Обработай каждый модуль из списка`,
988
+ `- Для event_name используй формат <domain>.<entity>.<action>`,
989
+ `- Для error_code используй коды из словаря (config/logging-error-codes.json)`,
990
+ ``,
991
+ `\n${LOGGING_STANDARD_INLINE}`,
992
+ ].filter(Boolean).join('\n');
993
+ }
994
+ function buildObsFixModulePrompt(modulePath, catalogItem) {
995
+ const isSuppressRec = catalogItem.recommendation === 'suppress';
996
+ const noisySnippets = (catalogItem.noisyMessages || []).slice(0, 6);
997
+ const recActions = {
998
+ 'suppress': 'Удали или структурируй шумные INFO/DEBUG/TRACE логи (WARN/ERROR не трогать!)',
999
+ 'downgrade level': 'Понизь уровень только INFO-логов без ценности → debug. WARN/ERROR не трогать.',
1000
+ 'enrich fields': 'Добавь недостающие обязательные поля',
1001
+ 'add event': 'Добавь warn/error события для обработки ошибок',
1002
+ };
1003
+ return [
1004
+ `Исправь логирование в модуле \`${modulePath}\`.`,
1005
+ ``,
1006
+ `Текущее состояние:`,
1007
+ `- Формат: ${catalogItem.format}`,
1008
+ `- Уровень: ${catalogItem.level}`,
1009
+ `- Пропущенные поля: ${catalogItem.missingFields.length > 0 ? catalogItem.missingFields.join(', ') : 'нет'}`,
1010
+ `- Рекомендация: ${catalogItem.recommendation}`,
1011
+ ``,
1012
+ isSuppressRec ? SUPPRESS_GUARD : '',
1013
+ ``,
1014
+ `Что сделать:`,
1015
+ `- ${recActions[catalogItem.recommendation] || catalogItem.recommendation}`,
1016
+ isSuppressRec && noisySnippets.length > 0
1017
+ ? `- Конкретные шумные сообщения для поиска:\n${noisySnippets.map(m => ` • "${m}"`).join('\n')}`
1018
+ : '',
1019
+ catalogItem.missingFields.length > 0
1020
+ ? `- Добавь поля: ${catalogItem.missingFields.join(', ')}`
1021
+ : '',
1022
+ catalogItem.format !== 'structured'
1023
+ ? `- Переведи неструктурированные вызовы (console.log/console.error) в структурированный JSON через logger`
1024
+ : '',
1025
+ `- Именование event_name: <domain>.<entity>.<action> (lower_snake_case через точку)`,
1026
+ ``,
1027
+ `\n${LOGGING_STANDARD_INLINE}`,
1028
+ ].filter(Boolean).join('\n');
1029
+ }
1030
+ function buildObsFixSelectedPrompt(selectedItems, meta) {
1031
+ const fieldName = meta.fieldName;
1032
+ const recommendationType = meta.recommendationType;
1033
+ const isSuppressTask = recommendationType === 'suppress' ||
1034
+ selectedItems.every(ci => ci.recommendation === 'suppress');
1035
+ const moduleList = selectedItems.map(ci => {
1036
+ const mf = ci.missingFields.length > 0 ? ci.missingFields.join(', ') : 'нет';
1037
+ const snippets = (ci.noisyMessages || []).slice(0, 4);
1038
+ const snippetBlock = snippets.length > 0
1039
+ ? `\n Шумные сообщения для удаления/структуризации:\n${snippets.map(m => ` • "${m}"`).join('\n')}`
1040
+ : '';
1041
+ return `- \`${ci.modulePath}\` (format: ${ci.format}, level: ${ci.level}, missing: ${mf})${snippetBlock}`;
1042
+ }).join('\n');
1043
+ let actionBlock;
1044
+ if (fieldName) {
1045
+ const fieldHints = {
1046
+ service: 'Имя сервиса. Берётся из конфига или env-переменной.',
1047
+ env: 'Среда (local|dev|stage|prod). Берётся из NODE_ENV.',
1048
+ trace_id: 'ID распределённого трейса. Из middleware или заголовка.',
1049
+ request_id: 'Сквозной ID запроса. Из заголовка X-Request-Id или middleware.',
1050
+ event_name: 'Доменное событие. Формат: <domain>.<entity>.<action>.',
1051
+ outcome: 'Результат: success|failure|partial.',
1052
+ error_code: 'Код ошибки из словаря. Обязателен для WARN/ERROR.',
1053
+ user_id: 'ID пользователя (user_id или user_hash). Из контекста auth.',
1054
+ };
1055
+ actionBlock = [
1056
+ `Задача: добавь поле \`${fieldName}\` во все лог-вызовы, где оно отсутствует.`,
1057
+ fieldHints[fieldName] ? `Описание поля: ${fieldHints[fieldName]}` : '',
1058
+ `Значение поля должно быть взято из контекста (request, config, env) — не хардкодь.`,
1059
+ ].filter(Boolean).join('\n');
1060
+ }
1061
+ else if (recommendationType === 'suppress') {
1062
+ actionBlock = [
1063
+ `Задача: убери шумные лог-вызовы уровня INFO/DEBUG/TRACE в каждом модуле.`,
1064
+ ``,
1065
+ SUPPRESS_GUARD,
1066
+ ``,
1067
+ `Для каждого шумного сообщения из списка ниже:`,
1068
+ `- УДАЛИ, если это lifecycle-мусор ("started", "done", "ok", "loaded", "ready")`,
1069
+ `- СТРУКТУРИРУЙ в logger.debug({ service, event_name, outcome, ...данные }), если несёт ценность`,
1070
+ ].join('\n');
1071
+ }
1072
+ else if (recommendationType) {
1073
+ const actionMap = {
1074
+ 'downgrade level': 'Понизь уровень логирования (info→debug) только для INFO-логов без диагностической ценности. WARN/ERROR не трогать.',
1075
+ 'enrich fields': 'Добавь недостающие обязательные поля в лог-вызовы',
1076
+ 'add event': 'Добавь warn/error события для обработки ошибок',
1077
+ };
1078
+ actionBlock = `Задача: ${actionMap[recommendationType] || recommendationType} в каждом модуле из списка.`;
1079
+ }
1080
+ else {
1081
+ actionBlock = 'Исправь логирование в каждом модуле из списка согласно его рекомендации.';
1082
+ }
1083
+ return [
1084
+ `Исправь логирование в ${selectedItems.length} модулях.`,
1085
+ ``,
1086
+ actionBlock,
1087
+ ``,
1088
+ `Модули:\n${moduleList}`,
1089
+ ``,
1090
+ `Требования:`,
1091
+ `- Обработай каждый модуль из списка`,
1092
+ `- Для event_name используй формат <domain>.<entity>.<action> (lower_snake_case через точку)`,
1093
+ `- Для error_code используй коды из словаря (config/logging-error-codes.json)`,
1094
+ `- Переведи console.* вызовы в структурированный logger`,
1095
+ ``,
1096
+ `\n${LOGGING_STANDARD_INLINE}`,
1097
+ ].filter(Boolean).join('\n');
1098
+ }
791
1099
  function buildE2ePlanPrompt(feat, modules) {
792
1100
  const files = modules
793
1101
  .filter(m => m.featureKeys.includes(feat.key) && m.type !== 'test' && !m.isInfra)
@@ -1244,6 +1552,80 @@ function startServer({ data: initialData, port, projectRoot }) {
1244
1552
  }
1245
1553
  prompt = buildWriteE2eTestPrompt(feat, plan, currentData.modules);
1246
1554
  }
1555
+ else if (task === 'obs-suppress-pattern') {
1556
+ const obs = currentData.observability;
1557
+ if (!obs || !item.meta?.pattern) {
1558
+ failBeforeStart('Нет данных observability или паттерн не указан');
1559
+ return;
1560
+ }
1561
+ prompt = buildObsSuppressPatternPrompt(item.meta.pattern, item.meta.recommendation || 'suppress', obs.catalog);
1562
+ }
1563
+ else if (task === 'obs-add-critical-logs') {
1564
+ const obs = currentData.observability;
1565
+ if (!obs || !item.meta?.modulePath) {
1566
+ failBeforeStart('Нет данных observability или модуль не указан');
1567
+ return;
1568
+ }
1569
+ const v2Item = (obs.missingCriticalLogsV2 || []).find((m) => m.modulePath === item.meta.modulePath);
1570
+ prompt = v2Item
1571
+ ? buildObsAddCriticalLogsPromptV2(v2Item, obs.catalog)
1572
+ : buildObsAddCriticalLogsPrompt(item.meta.modulePath, obs.catalog);
1573
+ }
1574
+ else if (task === 'obs-enrich-field') {
1575
+ const obs = currentData.observability;
1576
+ if (!obs || !item.meta?.fieldName) {
1577
+ failBeforeStart('Нет данных observability или поле не указано');
1578
+ return;
1579
+ }
1580
+ prompt = buildObsEnrichFieldPrompt(item.meta.fieldName, obs.catalog);
1581
+ }
1582
+ else if (task === 'obs-batch-recommendation') {
1583
+ const obs = currentData.observability;
1584
+ if (!obs || !item.meta?.recommendationType) {
1585
+ failBeforeStart('Нет данных observability или тип рекомендации не указан');
1586
+ return;
1587
+ }
1588
+ prompt = buildObsBatchRecommendationPrompt(item.meta.recommendationType, obs.catalog);
1589
+ }
1590
+ else if (task === 'obs-fix-module') {
1591
+ const obs = currentData.observability;
1592
+ const idx = item.meta?.catalogIndex;
1593
+ const catalogItem = typeof idx === 'number' ? obs?.catalog[idx] : null;
1594
+ if (!obs || !catalogItem) {
1595
+ failBeforeStart('Нет данных observability или модуль не найден в каталоге');
1596
+ return;
1597
+ }
1598
+ prompt = buildObsFixModulePrompt(catalogItem.modulePath, catalogItem);
1599
+ }
1600
+ else if (task === 'obs-fix-selected') {
1601
+ const obs = currentData.observability;
1602
+ const missingLogIndices = Array.isArray(item.meta?.missingLogIndices) ? item.meta.missingLogIndices : [];
1603
+ const indices = Array.isArray(item.meta?.catalogIndices) ? item.meta.catalogIndices : [];
1604
+ if (missingLogIndices.length > 0 && obs?.missingCriticalLogsV2) {
1605
+ // V2 batch: add critical logs to selected modules with failure points
1606
+ const selectedV2 = missingLogIndices
1607
+ .map(i => obs.missingCriticalLogsV2[i])
1608
+ .filter(Boolean);
1609
+ if (selectedV2.length === 0) {
1610
+ failBeforeStart('Выбранные модули не найдены');
1611
+ return;
1612
+ }
1613
+ prompt = buildObsBatchAddCriticalLogsPrompt(selectedV2, obs.catalog);
1614
+ }
1615
+ else if (indices.length > 0 && obs) {
1616
+ // Existing catalog-based flow
1617
+ const selectedItems = indices.map(i => obs.catalog[i]).filter(Boolean);
1618
+ if (selectedItems.length === 0) {
1619
+ failBeforeStart('Выбранные модули не найдены в каталоге');
1620
+ return;
1621
+ }
1622
+ prompt = buildObsFixSelectedPrompt(selectedItems, item.meta || {});
1623
+ }
1624
+ else {
1625
+ failBeforeStart('Нет данных observability или модули не выбраны');
1626
+ return;
1627
+ }
1628
+ }
1247
1629
  else {
1248
1630
  prompt = buildMapUnmappedPrompt(currentData.modules, currentData.features || []);
1249
1631
  }
@@ -1274,7 +1656,6 @@ function startServer({ data: initialData, port, projectRoot }) {
1274
1656
  cwd: projectRoot,
1275
1657
  shell: true,
1276
1658
  stdio: ['ignore', 'pipe', 'pipe'],
1277
- env: buildAgentSpawnEnv(agent),
1278
1659
  });
1279
1660
  activeAgentProcess = proc;
1280
1661
  emitOutput(`🚀 Запускаю: ${agent === 'claude' ? 'Claude Code' : 'Codex'}`);
@@ -1642,7 +2023,7 @@ function startServer({ data: initialData, port, projectRoot }) {
1642
2023
  });
1643
2024
  }
1644
2025
  /** Validate task params and enqueue (prompt is built lazily at execution time) */
1645
- function runAgent(task, featureKey, filePath, selectedFilePaths) {
2026
+ function runAgent(task, featureKey, filePath, selectedFilePaths, meta) {
1646
2027
  const agent = currentData.agent;
1647
2028
  if (!agent) {
1648
2029
  broadcast('agent-error', { message: 'Агент не выбран. Укажи agent в viberadar.config.json' });
@@ -1739,6 +2120,33 @@ function startServer({ data: initialData, port, projectRoot }) {
1739
2120
  }
1740
2121
  title = `${agentLabel} — пишу E2E тесты для "${feat.label}"`;
1741
2122
  }
2123
+ else if (task === 'obs-suppress-pattern') {
2124
+ const pattern = meta?.pattern || 'unknown';
2125
+ title = `${agentLabel} — убрать шумный паттерн "${String(pattern).slice(0, 40)}"`;
2126
+ }
2127
+ else if (task === 'obs-add-critical-logs') {
2128
+ const modulePath = meta?.modulePath || 'unknown';
2129
+ title = `${agentLabel} — добавить критичные логи в "${modulePath}"`;
2130
+ }
2131
+ else if (task === 'obs-enrich-field') {
2132
+ const fieldName = meta?.fieldName || 'unknown';
2133
+ title = `${agentLabel} — обогатить поле "${fieldName}"`;
2134
+ }
2135
+ else if (task === 'obs-batch-recommendation') {
2136
+ const recType = meta?.recommendationType || 'unknown';
2137
+ title = `${agentLabel} — исправить все (${recType})`;
2138
+ }
2139
+ else if (task === 'obs-fix-module') {
2140
+ const idx = meta?.catalogIndex;
2141
+ const catItem = typeof idx === 'number' ? currentData.observability?.catalog[idx] : null;
2142
+ const modName = catItem?.modulePath || 'unknown';
2143
+ title = `${agentLabel} — исправить логи в "${modName}"`;
2144
+ }
2145
+ else if (task === 'obs-fix-selected') {
2146
+ const count = Array.isArray(meta?.catalogIndices) ? meta.catalogIndices.length : 0;
2147
+ const label = meta?.fieldName ? `поле ${meta.fieldName}` : meta?.recommendationType || 'логи';
2148
+ title = `${agentLabel} — ${label} (${count} модулей)`;
2149
+ }
1742
2150
  else {
1743
2151
  title = `${agentLabel} — разобрать unmapped`;
1744
2152
  }
@@ -1753,6 +2161,7 @@ function startServer({ data: initialData, port, projectRoot }) {
1753
2161
  savedErrors,
1754
2162
  savedFailedFiles,
1755
2163
  savedTestType,
2164
+ meta,
1756
2165
  };
1757
2166
  createRun(item, 'queued');
1758
2167
  if (agentRunning || queueCooldownTimer) {
@@ -1838,6 +2247,46 @@ function startServer({ data: initialData, port, projectRoot }) {
1838
2247
  res.end(JSON.stringify({ ...currentData, testErrors, hasPlaywright: hasPlaywright(projectRoot), e2ePlansExist, agentRuntime }));
1839
2248
  return;
1840
2249
  }
2250
+ if (url === '/api/rescan' && req.method === 'POST') {
2251
+ (async () => {
2252
+ try {
2253
+ currentData = await (0, scanner_1.scanProject)(projectRoot);
2254
+ const testErrors = {};
2255
+ for (const [fp, detail] of lastTestResults) {
2256
+ const rel = path.relative(projectRoot, fp).replace(/\\/g, '/');
2257
+ testErrors[rel] = detail;
2258
+ }
2259
+ const e2ePlansExist = {};
2260
+ try {
2261
+ const planDir = e2ePlanDir(projectRoot);
2262
+ if (fs.existsSync(planDir)) {
2263
+ for (const f of fs.readdirSync(planDir)) {
2264
+ if (f.endsWith('.json'))
2265
+ e2ePlansExist[f.replace('.json', '')] = true;
2266
+ }
2267
+ }
2268
+ }
2269
+ catch { }
2270
+ const agentRuntime = {
2271
+ codexSandboxMode: runtimeEnv.codexSandboxMode,
2272
+ approvalPolicy: runtimeEnv.approvalPolicy,
2273
+ queueMax: runtimeEnv.agentQueueMax,
2274
+ cooldownMinMs: runtimeEnv.agentCooldownMinMs,
2275
+ cooldownMaxMs: runtimeEnv.agentCooldownMaxMs,
2276
+ autoFixFailedTests: runtimeEnv.autoFixFailedTests,
2277
+ autoFixMaxRetries: runtimeEnv.autoFixMaxRetries,
2278
+ };
2279
+ broadcast('data-updated');
2280
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2281
+ res.end(JSON.stringify({ ...currentData, testErrors, hasPlaywright: hasPlaywright(projectRoot), e2ePlansExist, agentRuntime }));
2282
+ }
2283
+ catch (err) {
2284
+ res.writeHead(500, { 'Content-Type': 'application/json' });
2285
+ res.end(JSON.stringify({ error: err.message }));
2286
+ }
2287
+ })();
2288
+ return;
2289
+ }
1841
2290
  if (url === '/api/status') {
1842
2291
  res.writeHead(200, { 'Content-Type': 'application/json' });
1843
2292
  res.end(JSON.stringify({
@@ -2064,9 +2513,9 @@ function startServer({ data: initialData, port, projectRoot }) {
2064
2513
  req.on('data', d => body += d);
2065
2514
  req.on('end', () => {
2066
2515
  try {
2067
- const { task, featureKey, filePath, selectedFilePaths } = JSON.parse(body);
2068
- process.stdout.write(` 📥 run-agent: task=${task} featureKey=${featureKey} filePath=${filePath} selected=${Array.isArray(selectedFilePaths) ? selectedFilePaths.length : 0}\n`);
2069
- const runId = runAgent(task, featureKey, filePath, selectedFilePaths);
2516
+ const { task, featureKey, filePath, selectedFilePaths, meta } = JSON.parse(body);
2517
+ process.stdout.write(` 📥 run-agent: task=${task} featureKey=${featureKey} filePath=${filePath} selected=${Array.isArray(selectedFilePaths) ? selectedFilePaths.length : 0} meta=${meta ? 'yes' : 'no'}\n`);
2518
+ const runId = runAgent(task, featureKey, filePath, selectedFilePaths, meta);
2070
2519
  res.writeHead(200, { 'Content-Type': 'application/json' });
2071
2520
  res.end(JSON.stringify({ ok: true, runId }));
2072
2521
  }