Все статьи

30 сентября 2026 г.

Анализ влияния изменений в общем модуле 1С через граф связей: от grep до MCP-ассистента

Почему правка общего модуля — это всегда минное поле

Любой архитектор 1С знает это чувство: нужно переименовать параметр в ОбщегоНазначения.ЗначениеРеквизитаОбъекта или вынести часть логики из ОбщегоНазначенияКлиентСервер. Метод вызывается из 400 мест, из них 60 — в расширениях, 15 — в подписках на события, а 3 — в динамически формируемых именах через Выполнить().

Стандартный путь разработчика выглядит так:

  1. Поиск по конфигурации через Ctrl+Shift+F по имени метода.
  2. Ручная фильтрация ложных срабатываний (комментарии, строковые литералы, одноимённые методы другого модуля).
  3. Открытие каждого модуля, чтобы понять контекст вызова.
  4. Надежда, что ничего не забыл.

Проблема в том, что типичная ERP или УТ — это гигабайты XML и сотни тысяч строк BSL. Grep находит совпадения, но не различает:

  • вызов метода из другого общего модуля;
  • вызов через ОбщегоНазначения.ВызватьМетодПоИмени();
  • подписку на событие ПередЗаписью, которая косвенно зависит от вашего метода;
  • использование в запросе через вычисляемое поле;
  • вызов из формы, который «спрятан» в &НаКлиенте.

Что именно должен показывать граф связей

Граф зависимостей конфигурации 1С — это ориентированный мультиграф, где узлы — это сущности метаданных и кода, а рёбра — типизированные связи между ними.

| Тип узла | Пример | Тип ребра |
|---|---|---|
| Общий модуль | ОбщегоНазначения | экспортирует метод |
| Процедура/функция | ЗначениеРеквизитаОбъекта | вызывает |
| Подписка на событие | ПередЗаписью_ОбработкаПроведения | обрабатывает событие объекта |
| Форма | Документ.РеализацияТоваровУслуг.Форма.ФормаДокумента | использует модуль |
| Запрос | текстовый ресурс ТекстЗапроса | обращается к таблице |
| Расширение | Расш1_ОбщегоНазначения | переопределяет метод |

Без графа вы видите плоский список совпадений. С графом вы видите транзитивное замыкание — цепочку «кто вызывает → чем вызывается → на что влияет».

Архитектура Графона: локальный разбор + MCP

Графон работает полностью локально, без отправки кода конфигурации в облако:

┌──────────────────────────┐
│  Выгрузка конфигурации   │  XML + BSL
│  (ERP, УТ, ЗУП, КА)      │
└───────────┬──────────────┘
            │ индексация
            ▼
┌──────────────────────────┐
│  Парсер метаданных и BSL │  сигнатуры, вызовы,
│  + резолвер ссылок       │  подписки, запросы
└───────────┬──────────────┘
            ▼
┌──────────────────────────┐
│  Граф зависимостей       │  SQLite / in-memory
│  (узлы, рёбра, атрибуты) │
└───────────┬──────────────┘
            │ MCP (stdio / HTTP)
            ▼
┌──────────────────────────┐
│  Claude Code / Cursor /  │  получает только срез
│  Windsurf / Roo Code     │  связанных файлов
└──────────────────────────┘

Ключевая идея: ИИ-агент не читает всю конфигурацию. Он обращается к MCP-серверу Графона и получает ровно тот подграф, который нужен для задачи.

Практический сценарий: меняем сигнатуру метода

Допустим, в общем модуле ОбщегоНазначения есть функция:

// Возвращает значение реквизита объекта по имени.
//
// Параметры:
//  Ссылка    - ДокументСсылка, СправочникСсылка - ссылка на объект
//  ИмяРеквизита - Строка - имя реквизита
//  Контекст  - Структура - дополнительный контекст (устаревший параметр)
//
// Возвращаемое значение:
//  Произвольный
//
Функция ЗначениеРеквизитаОбъекта(Ссылка, ИмяРеквизита, Контекст = Неопределено) Экспорт
    
    Если Контекст <> Неопределено Тогда
        // Устаревшая ветка, оставлена для совместимости с 2019 годом
        Возврат ЗначениеРеквизитаОбъектаУстаревший(Ссылка, ИмяРеквизита, Контекст);
    КонецЕсли;
    
    Возврат ОбщегоНазначенияКлиентСервер.ЗначениеРеквизитаОбъекта(Ссылка, ИмяРеквизита);
    
КонецФункции

Мы хотим убрать третий параметр. Первый шаг — запрос к Графону через MCP:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "analyze_impact",
    "arguments": {
      "symbol": "ОбщегоНазначения.ЗначениеРеквизитаОбъекта",
      "direction": "callers",
      "depth": 3,
      "includeExtensions": true,
      "includeSubscriptions": true,
      "includeDynamicCalls": true
    }
  }
}

Графон возвращает структурированный ответ:

{
  "symbol": "ОбщегоНазначения.ЗначениеРеквизитаОбъекта",
  "signature_changed": true,
  "callers": [
    {
      "module": "Документ.РеализацияТоваровУслуг.МодульОбъекта",
      "method": "ПередЗаписью",
      "line": 214,
      "uses_param": "Контекст",
      "risk": "high"
    },
    {
      "module": "ОбщийМодуль.Расш1_ОбщегоНазначения",
      "method": "ЗначениеРеквизитаОбъекта",
      "line": 8,
      "risk": "extension_override"
    },
    {
      "module": "Подписка.ПередЗаписью_КонтрольОстатков",
      "method": "Обработчик",
      "line": 57,
      "uses_param": null,
      "risk": "medium"
    }
  ],
  "dynamic_calls": [
    "ОбщийМодуль.Мониторинг.ВызватьМетодПоИмени(\"ОбщегоНазначения\", \"ЗначениеРеквизитаОбъекта\", ...)"
  ],
  "total_callers": 412,
  "callers_using_removed_param": 63
}

Вместо 412 открытых модулей и ручного анализа вы получаете точный список из 63 мест, где реально используется удаляемый параметр. Остальные 349 — безопасны.

Шаг 2. Получаем минимальный срез кода

Вместо того чтобы скармливать агенту весь модуль ОбщегоНазначения (4 500 строк, ~60 000 токенов), запрашиваем только нужные сигнатуры:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "get_context_slice",
    "arguments": {
      "symbols": ["ОбщегоНазначения.ЗначениеРеквизитаОбъекта"],
      "includeCallers": true,
      "callersLimit": 63,
      "maxTokens": 12000
    }
  }
}

Шаг 3. Автоматическая правка и проверка

После правки агент может запросить обратную проверку:

{
  "name": "verify_refactor",
  "arguments": {
    "removed_param": "Контекст",
    "symbol": "ОбщегоНазначения.ЗначениеРеквизитаОбъекта"
  }
}

Графон вернёт список оставшихся вызовов с тремя аргументами — ни один не будет забыт.

Сравнение: с Графоном и без

| Метрика | Без Графона (ИИ-агент + полный контекст) | С Графоном (MCP-срез) |
|---|---|---|
| Токенов на анализ ERP | ~480 000 | ~11 500 |
| Время первичного анализа | 12–18 минут | 40–70 секунд |
| Ошибок «метод не найден» / галлюцинаций | 8–15 за сессию | 0–1 |
| Пропущенных вызовов в расширениях | 10–30 % | 0 % |
| Стоимость одной итерации рефакторинга | $3–7 | $0,20–0,45 |
| Учёт подписок на события | вручную | автоматически |
| Динамические вызовы (Выполнить, ВызватьМетодПоИмени) | не находятся | находятся |

Разница в расходе токенов достигает 10× — именно за счёт того, что агент не «читает всю конфигурацию», а получает граф-срез.

Подписки, формы и запросы — невидимая часть айсберга

Самое опасное при изменении общего модуля — косвенные зависимости. Графон строит их через отдельные рёбра графа:

// Подписка на событие: ПередЗаписью документа РеализацияТоваровУслуг
Процедура Расш1_ПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
    
    // Косвенная зависимость: метод общего модуля вызывается внутри обработчика
    Остатки = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(
        Источник.Ссылка, "Организация", Неопределено);
    
    Если Остатки = Неопределено Тогда
        Отказ = Истина;
    КонецЕсли;
    
КонецПроцедуры

MCP-запрос find_subscriptions с параметром usesSymbol покажет все подписки, чьи обработчики (прямо или через цепочку из N вызовов) зависят от изменяемого метода. Это то, что grep принципиально не умеет.

Аналогично для запросов:

{
  "name": "find_query_usages",
  "arguments": {
    "module": "ОбщегоНазначения",
    "symbol": "ЗначениеРеквизитаОбъекта"
  }
}

Обратный анализ: «кто вызывает» и «кого вызывает»

Граф двунаправленный. Помимо analyze_impact (direction: callers), полезен get_callees — что сам метод тянет за собой. Это критично, когда нужно понять, можно ли вынести логику без разрыва цепочки:

Функция ЗначениеРеквизитаОбъекта(Ссылка, ИмяРеквизита) Экспорт
    // дерево вызовов, которое вернёт Графон:
    // → ОбщегоНазначенияКлиентСервер.ЗначениеРеквизитаОбъекта
    //   → Метаданные.НайтиПоПолномуИмени (платформенный)
    // → ОбщегоНазначенияПовтИсп.КэшРеквизитов
    //   → ПовторноеИспользование.КэшРеквизитов
КонецФункции

Интеграция с Claude Code и Cursor

Графон подключается как MCP-сервер одной строкой в конфиг агента:

{
  "mcpServers": {
    "grafon": {
      "command": "grafon",
      "args": ["mcp", "--config", "./grafon.config.json"],
      "env": {
        "GRAFON_CONF_PATH": "/data/erp_3_1"
      }
    }
  }
}

После этого в промпте достаточно написать:

«Проанализируй влияние удаления параметра Контекст из ОбщегоНазначения.ЗначениеРеквизитаОбъекта, найди все вызовы и предложи патч».

Агент сам вызовет analyze_impact, get_context_slice и verify_refactor, не загружая конфигурацию целиком.

Чек-лист перед изменением общего модуля

  1. Получить список прямых вызовов (analyze_impact, depth=1).
  2. Расширить глубину до 3 — найти транзитивные зависимости.
  3. Проверить расширения (includeExtensions: true).
  4. Проверить подписки на события (includeSubscriptions: true).
  5. Проверить динамические вызовы (includeDynamicCalls: true).
  6. Сопоставить изменения в ОбщийМодульПовтИсп и кэшах.
  7. Выполнить verify_refactor после правки.

Итоги

Анализ влияния изменений в общем модуле 1С через граф связей превращает самую рискованную операцию в предсказуемую. Графон строит этот граф локально, а через MCP отдаёт ИИ-агенту ровно тот срез, который нужен: сигнатуры, вызовы, подписки, запросы. Результат — до 10× меньше токенов, скорость анализа в десятки раз выше и отсутствие пропущенных вызовов, из-за которых обычно и падают релизы ERP и УТ.