From aa69776807159bdfdd30029d26c344cd36ac1d73 Mon Sep 17 00:00:00 2001 From: busya Date: Mon, 8 Sep 2025 16:23:03 +0300 Subject: [PATCH] update documentator promt --- .../knowledge_base/ai_friendly_logging.md | 52 -- .../knowledge_base/ai_friendly_logging.xml | 52 ++ .../knowledge_base/design_by_contract.md | 35 -- .../knowledge_base/design_by_contract.xml | 55 ++ .../knowledge_base/graphrag_optimization.md | 76 --- .../knowledge_base/graphrag_optimization.xml | 55 ++ .../knowledge_base/semantic_linting.xml | 10 +- .../semantic_enrichment_protocol.xml | 2 +- agent_promts/roles/documentation.xml | 117 ++-- .../screen/itemedit/ItemEditViewModelTest.kt | 5 +- buildSrc/src/main/java/Dependencies.kt | 1 + .../ktlint/rules/ExampleInstrumentedTest.kt | 4 + .../ktlint/rules/CustomRuleSetProvider.kt | 4 +- .../com/busya/ktlint/rules/FileHeaderRule.kt | 4 +- .../rules/MandatoryEntityDeclarationRule.kt | 4 +- .../busya/ktlint/rules/NoStrayCommentsRule.kt | 4 +- .../com/busya/ktlint/rules/ExampleUnitTest.kt | 4 + extract_semantics.py | 501 ++++++++++++++++++ 18 files changed, 739 insertions(+), 246 deletions(-) delete mode 100644 agent_promts/knowledge_base/ai_friendly_logging.md create mode 100644 agent_promts/knowledge_base/ai_friendly_logging.xml delete mode 100644 agent_promts/knowledge_base/design_by_contract.md create mode 100644 agent_promts/knowledge_base/design_by_contract.xml delete mode 100644 agent_promts/knowledge_base/graphrag_optimization.md create mode 100644 agent_promts/knowledge_base/graphrag_optimization.xml create mode 100644 extract_semantics.py diff --git a/agent_promts/knowledge_base/ai_friendly_logging.md b/agent_promts/knowledge_base/ai_friendly_logging.md deleted file mode 100644 index af05e05..0000000 --- a/agent_promts/knowledge_base/ai_friendly_logging.md +++ /dev/null @@ -1,52 +0,0 @@ -[AIFriendlyLogging] -**Tags:** LOGGING, TRACEABILITY, STRUCTURED_LOG, DEBUG, CLEAN_ARCHITECTURE - -> Логирование — это мой критически важный механизм для декларации `belief state` (внутреннего состояния/намерения) и трассировки выполнения кода. Каждая значимая операция, проверка контракта или изменение состояния ДОЛЖНЫ сопровождаться структурированной записью в лог. Это делает поведение кода в рантайме полностью прозрачным и отлаживаемым. - -## Rules - -### ArchitecturalBoundaryCompliance -Логирование в его прямой реализации (т.е. вызов `logger.info`, `Timber.i` и т.д.) **КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО** внутри модуля `:domain`. - -> `Согласно принципам чистой архитектуры, слой `domain` должен быть полностью независим от внешних фреймворков и платформ (включая Android). Его задача — содержать исключительно бизнес-логику. Логирование, как и другие инфраструктурные задачи, должно выполняться в более внешних слоях, таких как `:data` или `:app`.` - -### StructuredLogFormat -Все записи в лог должны строго следовать этому формату для обеспечения машиночитаемости и консистентности. - -``` -`logger.level("[LEVEL][ANCHOR_NAME][BELIEF_STATE] Message with {} placeholders for data.")` -``` - -### ComponentDefinitions - -#### Components -- **[LEVEL]**: Один из стандартных уровней логирования: `DEBUG`, `INFO`, `WARN`, `ERROR`. Я также использую специальный уровень `CONTRACT_VIOLATION` для логов, связанных с провалом `require` или `check`. -- **[ANCHOR_NAME]**: Точное имя семантического якоря из кода, к которому относится данный лог. Это создает неразрывную связь между статическим кодом и его выполнением. Например: `[ENTRYPOINT]`, `[ACTION]`, `[PRECONDITION]`, `[FALLBACK]`. -- **[BELIEF_STATE]**: Краткое, четкое описание моего намерения в `snake_case`. Это отвечает на вопрос 'почему' я выполняю этот код. Примеры: `validating_input`, `calling_external_api`, `mutating_state`, `persisting_data`, `handling_exception`, `mapping_dto`. - -### Example -Вот как я применяю этот стандарт на практике внутри функции: -```kotlin -// ... -// [ENTRYPOINT] -suspend fun processPayment(request: PaymentRequest): Result { - logger.info("[INFO][ENTRYPOINT][processing_payment] Starting payment process for request '{}'.", request.id) - - // [PRECONDITION] - logger.debug("[DEBUG][PRECONDITION][validating_input] Validating payment request.") - require(request.amount > 0) { "Payment amount must be positive." } - - // [ACTION] - logger.info("[INFO][ACTION][calling_external_api] Calling payment gateway for amount {}."), request.amount) - val result = paymentGateway.execute(request) - - // ... -} -``` - -### TraceabilityIsMandatory -Каждая запись в логе ДОЛЖНА быть семантически привязана к якорю в коде. Логи без якоря запрещены. Это не опция, а фундаментальное требование для обеспечения полной трассируемости потока выполнения. - -### DataAsArguments_NotStrings -Данные (переменные, значения) должны передаваться в логгер как отдельные аргументы, а не встраиваться в строку сообщения. Я использую плейсхолдеры `{}`. Это повышает производительность и позволяет системам сбора логов индексировать эти данные. -[/End AIFriendlyLogging] diff --git a/agent_promts/knowledge_base/ai_friendly_logging.xml b/agent_promts/knowledge_base/ai_friendly_logging.xml new file mode 100644 index 0000000..78fae1b --- /dev/null +++ b/agent_promts/knowledge_base/ai_friendly_logging.xml @@ -0,0 +1,52 @@ + + + + + + Каждая значимая операция, проверка контракта или изменение состояния ДОЛЖНЫ + сопровождаться структурированной записью в лог для обеспечения полной + трассируемости и отлаживаемости. + + + Структурированные логи превращают поток выполнения программы из "черного ящика" + в машиночитаемый и анализируемый артефакт, связывая рантайм-поведение + со статическим кодом через якоря. + + + + + + + Все вызовы логгера должны соответствовать формату [LEVEL][ANCHOR][STATE]... + + Нарушен структурный формат лога. Ожидается: [LEVEL][ANCHOR][STATE] message. + + + + + Данные должны передаваться как аргументы, а не через строковую интерполяцию (запрещено использовать '$' в строке лога). + + Обнаружена строковая интерполяция ('$') в сообщении лога. Передавайте данные как аргументы. + + + + + Прямые вызовы логгера (logger.*, Timber.*) запрещены в модуле :domain. + + Обнаружен прямой вызов логгера в модуле :domain, что нарушает принципы чистой архитектуры. + + + + \ No newline at end of file diff --git a/agent_promts/knowledge_base/design_by_contract.md b/agent_promts/knowledge_base/design_by_contract.md deleted file mode 100644 index 3a14e76..0000000 --- a/agent_promts/knowledge_base/design_by_contract.md +++ /dev/null @@ -1,35 +0,0 @@ -[DesignByContractAsFoundation] -**Tags:** DBC, CONTRACT, PRECONDITION, POSTCONDITION, INVARIANT, KDOC, REQUIRE, CHECK - -> Принцип 'Проектирование по контракту' (DbC) — это не опция, а фундаментальная основа моего подхода к разработке. Каждая функция и класс, которые я создаю, являются реализацией формального контракта между поставщиком (код) и клиентом (вызывающий код). Это устраняет двусмысленность, предотвращает ошибки и делает код самодокументируемым и предсказуемым. - -## Rules - -### ContractFirstMindset -Я всегда начинаю с проектирования и написания KDoc-контракта. Код является реализацией этой формальной спецификации. Проверки контракта (`require`, `check`) создаются до или вместе с основной логикой, а не после как запоздалая мысль. - -### KDocAsFormalSpecification -KDoc-блок является человекочитаемой формальной спецификацией контракта. Для правильной обработки механизмом Causal Attention, он ВСЕГДА предшествует блоку семантической разметки и декларации функции/класса. Я использую стандартизированный набор тегов для полного описания контракта. - -#### Tags -- **@param**: Описывает **предусловия** для конкретного параметра. Что клиент должен гарантировать. -- **@return**: Описывает **постусловия** для возвращаемого значения. Что поставщик гарантирует в случае успеха. -- **@throws**: Описывает условия (обычно нарушение предусловий), при которых будет выброшено исключение. Это часть 'негативного' контракта. -- **@invariant**: (для класса) Явно описывает **инвариант** класса — условие, которое должно быть истинным всегда, когда объект не выполняет метод. -- **@sideeffect**: Четко декларирует любые побочные эффекты (запись в БД, сетевой вызов, изменение внешнего состояния). Если их нет, я явно указываю `@sideeffect Отсутствуют.`. - -### PreconditionsWithRequire -Предусловия (обязательства клиента) должны быть проверены в самом начале публичного метода с использованием `require(condition) { "Error message" }`. Это реализует принцип 'Fail-Fast' — немедленный отказ, если клиент нарушил контракт. - -**Location:** Первые исполняемые строки кода внутри тела функции, сразу после лога `[ENTRYPOINT]`. - -### PostconditionsWithCheck -Постусловия (гарантии поставщика) должны быть проверены в самом конце метода, прямо перед возвратом управления, с использованием `check(condition) { "Error message" }`. Это самопроверка, гарантирующая, что моя работа выполнена правильно. - -**Location:** Последние строки кода внутри тела функции, непосредственно перед каждым оператором `return`. - -### InvariantsWithInitAndCheck -Инварианты класса (условия, которые всегда должны быть истинны для экземпляра) проверяются в двух местах: в блоке `init` для гарантии корректного создания объекта, и в конце каждого публичного метода, изменяющего состояние, с помощью `check(condition)`. - -**Location:** Блок `init` и конец каждого метода-мутатора. -[/End DesignByContractAsFoundation] diff --git a/agent_promts/knowledge_base/design_by_contract.xml b/agent_promts/knowledge_base/design_by_contract.xml new file mode 100644 index 0000000..6324852 --- /dev/null +++ b/agent_promts/knowledge_base/design_by_contract.xml @@ -0,0 +1,55 @@ + + + + + + Каждая публичная сущность должна иметь формальный KDoc-контракт, а предусловия + и постусловия должны быть реализованы в коде через require/check. + + + Это устраняет двусмысленность, предотвращает ошибки по принципу 'Fail-Fast' + и делает код самодокументируемым и предсказуемым. + + + + + + + Публичные функции и классы должны иметь полный KDoc-контракт. + + + + + + + + + + Отсутствует обязательный KDoc-тег контракта. + + + + + Предусловия, описанные в @param, должны проверяться через require(). + + Предусловие (@param) задекларировано в KDoc, но не проверяется с помощью require() в коде. + + + + + Постусловия, описанные в @return, должны проверяться через check(). + + Постусловие (@return) задекларировано в KDoc, но не проверяется с помощью check() в коде. + + + + + \ No newline at end of file diff --git a/agent_promts/knowledge_base/graphrag_optimization.md b/agent_promts/knowledge_base/graphrag_optimization.md deleted file mode 100644 index db5fb2c..0000000 --- a/agent_promts/knowledge_base/graphrag_optimization.md +++ /dev/null @@ -1,76 +0,0 @@ -[GraphRAG_Optimization] -**Tags:** GRAPH, RAG, ENTITY, RELATION, ARCHITECTURE, SEMANTIC_TRIPLET - -> Этот принцип является моей основной директивой по созданию 'самоописываемого' кода. Я встраиваю явный, машиночитаемый граф знаний непосредственно в исходный код. Цель — сделать архитектуру, зависимости и потоки данных очевидными и запрашиваемыми без необходимости в сложных инструментах статического анализа. Каждый файл становится фрагментом глобального графа знаний проекта. - -## Rules - -### Entity_Declaration_As_Graph_Nodes -Каждая архитектурно значимая сущность в коде должна быть явно объявлена как **узел (Node)** в нашем графе знаний. Для этого я использую якорь `[ENTITY]`. - -**Rationale:** Определение узлов — это первый шаг в построении любого графа. Без явно определенных сущностей невозможно описать связи между ними. Это создает 'существительные' в языке нашей архитектуры. - -**Format:** `// [ENTITY: EntityType('EntityName')]` - -#### Valid Types -- **Module**: Высокоуровневый модуль Gradle (e.g., 'app', 'data', 'domain'). -- **Class**: Стандартный класс. -- **Interface**: Интерфейс. -- **Object**: Синглтон-объект. -- **DataClass**: Класс данных (DTO, модель, состояние UI). -- **SealedInterface**: Запечатанный интерфейс (для состояний, событий). -- **EnumClass**: Класс перечисления. -- **Function**: Публичная, архитектурно значимая функция. -- **UseCase**: Класс, реализующий конкретный сценарий использования. -- **ViewModel**: ViewModel из архитектуры MVVM. -- **Repository**: Класс-репозиторий. -- **DataStructure**: Структура данных, которая не является `DataClass` (e.g., `Pair`, `Map`). -- **DatabaseTable**: Таблица в базе данных Room. -- **ApiEndpoint**: Конкретная конечная точка API. - -**Example:** -```kotlin -// [ENTITY: ViewModel('DashboardViewModel')] -class DashboardViewModel(...) { ... } -``` - -### Relation_Declaration_As_Graph_Edges -Все взаимодействия и зависимости между сущностями должны быть явно объявлены как **ребра (Edges)** в нашем графе знаний. Для этого я использую якорь `[RELATION]` в формате семантического триплета. - -**Rationale:** Ребра — это 'глаголы' в языке нашей архитектуры. Они делают неявные связи (как вызов метода или использование DTO) явными и машиночитаемыми. Это позволяет автоматически строить диаграммы зависимостей, анализировать влияние изменений и находить архитектурные проблемы. - -**Format:** `// [RELATION: 'SubjectType'('SubjectName')] -> [RELATION_TYPE] -> ['ObjectType'('ObjectName')]` - -#### Valid Relations -- **CALLS**: Субъект вызывает функцию/метод объекта. -- **CREATES_INSTANCE_OF**: Субъект создает экземпляр объекта. -- **INHERITS_FROM**: Субъект наследуется от объекта (для классов). -- **IMPLEMENTS**: Субъект реализует объект (для интерфейсов). -- **READS_FROM**: Субъект читает данные из объекта (e.g., DatabaseTable, Repository). -- **WRITES_TO**: Субъект записывает данные в объект. -- **MODIFIES_STATE_OF**: Субъект изменяет внутреннее состояние объекта. -- **DEPENDS_ON**: Субъект имеет зависимость от объекта (e.g., использует как параметр, DTO, или внедряется через DI). Это наиболее частая связь. -- **DISPATCHES_EVENT**: Субъект отправляет событие/сообщение определенного типа. -- **OBSERVES**: Субъект подписывается на обновления от объекта (e.g., Flow, LiveData). -- **TRIGGERS**: Субъект (обычно UI-событие или компонент) инициирует выполнение объекта (обычно функции ViewModel). -- **EMITS_STATE**: Субъект (обычно ViewModel или UseCase) является источником/производителем определённого состояния (DataClass). -- **CONSUMES_STATE**: Субъект (обычно UI-компонент или экран) потребляет/подписывается на определённое состояние (DataClass). - -**Example:** -```kotlin -// Пример для ViewModel, который зависит от UseCase и является источником состояния -// [ENTITY: ViewModel('DashboardViewModel')] -// [RELATION: ViewModel('DashboardViewModel')] -> [DEPENDS_ON] -> [UseCase('GetStatisticsUseCase')] -// [RELATION: ViewModel('DashboardViewModel')] -> [EMITS_STATE] -> [DataClass('DashboardUiState')] -class DashboardViewModel @Inject constructor( - private val getStatisticsUseCase: GetStatisticsUseCase -) : ViewModel() { ... } -``` - -### MarkupBlockCohesion -Вся семантическая разметка, относящаяся к одной сущности (`[ENTITY]` и все ее `[RELATION]` триплеты), должна быть сгруппирована в единый, непрерывный блок комментариев. - -**Rationale:** Это создает атомарный 'блок метаданных' для каждой сущности. Это упрощает парсинг и гарантирует, что весь архитектурный контекст считывается как единое целое, прежде чем AI-инструмент приступит к анализу самого кода. - -**Placement:** Этот блок всегда размещается непосредственно перед KDoc-блоком сущности или, если KDoc отсутствует, перед самой декларацией сущности. -[/End GraphRAG_Optimization] \ No newline at end of file diff --git a/agent_promts/knowledge_base/graphrag_optimization.xml b/agent_promts/knowledge_base/graphrag_optimization.xml new file mode 100644 index 0000000..039cc2c --- /dev/null +++ b/agent_promts/knowledge_base/graphrag_optimization.xml @@ -0,0 +1,55 @@ + + Код должен содержать явный, машиночитаемый граф знаний в виде семантических якорей [ENTITY] и [RELATION]. + Это делает архитектуру, зависимости и потоки данных очевидными и запрашиваемыми без необходимости в сложных инструментах статического анализа. + + + + + Блок семантической разметки ([ENTITY]/[RELATION]) должен предшествовать KDoc-контракту. + + + Нарушен порядок блоков: блок разметки ([ENTITY]/[RELATION]) должен быть определен ПЕРЕД KDoc-контрактом. + + + + + Тип сущности в якоре [ENTITY] должен принадлежать к предопределенной таксономии. + + ModuleClassInterfaceObject + DataClassSealedInterfaceEnumClassFunction + UseCaseViewModelRepositoryDataStructure + DatabaseTableApiEndpoint + + Использован невалидный тип сущности в якоре [ENTITY]. + + + + + Якоря [RELATION] должны соответствовать формату семантического триплета и использовать валидные типы связей. + \w+)'\('(?P.*?)'\)\s*->\s*\[(?P\w+)\]\s*->\s*\['(?P\w+)'\('(?P.*?)'\)\]]]> + + CALLSCREATES_INSTANCE_OFINHERITS_FROMIMPLEMENTS + READS_FROMWRITES_TOMODIFIES_STATE_OFDEPENDS_ON + DISPATCHES_EVENTOBSERVESTRIGGERSEMITS_STATECONSUMES_STATE + + Якорь [RELATION] имеет неверный формат или использует невалидный тип связи. + + + + + Вся семантическая разметка ([ENTITY] и [RELATION]) для одной сущности должна быть сгруппирована в единый непрерывный блок комментариев. + Нарушена целостность блока разметки: обнаружены строки кода или пустые строки между якорями [ENTITY] и [RELATION]. + + + + \ No newline at end of file diff --git a/agent_promts/knowledge_base/semantic_linting.xml b/agent_promts/knowledge_base/semantic_linting.xml index faaba83..a71ace5 100644 --- a/agent_promts/knowledge_base/semantic_linting.xml +++ b/agent_promts/knowledge_base/semantic_linting.xml @@ -30,10 +30,16 @@ package com.example.your.package.name uidomaindatapresentation - viewmodelusecaserepositoryservicescreencomponentdialogmodelentity + viewmodelusecaserepositoryservicescreencomponentdialogmodelentityactivityapplicationnav_hostcontrollernavigation_drawerscaffolddashboarditemlabellocationsetupthemedependenciescustom_fieldstatisticsimageattachmentitem_creationitem_detaileditem_summaryitem_updatesummaryupdate - networkingdatabasecachingauthenticationvalidationparsingstate_managementnavigationditesting + networkingdatabasecachingauthenticationvalidationparsingstate_managementnavigationditestingentrypointhilttimbercomposeactionsroutescommoncolor_selectionloadinglistdetailseditlabel_managementlabels_listdialog_managementlocationssealed_stateparallel_data_loadingtimber_loggingdialogcolortypographybuilddata_transfer_objectdtoapiitem_creationitem_detaileditem_summaryitem_updatecreatemappercountuser_setupauthentication_flow + + + sealed_classsealed_interface + + + ui_logicui_statedata_modelimmutable diff --git a/agent_promts/protocols/semantic_enrichment_protocol.xml b/agent_promts/protocols/semantic_enrichment_protocol.xml index af38e45..b2a1227 100644 --- a/agent_promts/protocols/semantic_enrichment_protocol.xml +++ b/agent_promts/protocols/semantic_enrichment_protocol.xml @@ -4,7 +4,7 @@ 1.0 - + diff --git a/agent_promts/roles/documentation.xml b/agent_promts/roles/documentation.xml index 7cb6dca..ecb83f9 100644 --- a/agent_promts/roles/documentation.xml +++ b/agent_promts/roles/documentation.xml @@ -1,24 +1,28 @@ + - Этот документ определяет операционный протокол для **исполнения роли 'Агента Документации'**. Главная задача — синхронизация `PROJECT_MANIFEST.xml` с текущим состоянием кодовой базы. - 5.2 - - - - - - + + Этот документ определяет операционный протокол для исполнения роли 'Агента Документации'. + Главная задача — синхронизация `PROJECT_MANIFEST.xml` с текущим состоянием кодовой базы. + Анализ кодовой базы выполняется с помощью внешнего Python-скрипта, который руководствуется + правилами из `semantic_protocol.xml`. + + 6.0 - ../interfaces/task_channel_interface.xml - - ../protocols/semantic_enrichment_protocol.xml + - ../protocols/semantic_protocol.xml - При исполнении этой роли, я, Gemini, действую как автоматизированный аудитор и синхронизатор проекта. Моя задача — обеспечить, чтобы `PROJECT_MANIFEST.xml` был точным отражением реального состояния кодовой базы. + + При исполнении этой роли, я, Gemini, действую как автоматизированный аудитор и оркестратор. + Моя задача — обеспечить, чтобы `PROJECT_MANIFEST.xml` был точным отражением реального + состояния кодовой базы, используя для анализа специализированные инструменты. + Поддерживать целостность и актуальность `PROJECT_MANIFEST.xml` и фиксировать его изменения через предоставленный канал задач. @@ -43,79 +47,42 @@ - find . -name "*.kt" + find . -path '*/build' -prune -o -name "*.kt" -print + python3 extract_semantics.py --protocol agent_promts/protocols/semantic_protocol.xml [file_list] - - Использовать `MyTaskChannel.FindNextTask(RoleName='agent-docs', TaskType='type::documentation')` для получения задачи. + + Найти и принять в работу задачу на синхронизацию манифеста. + Использовать `MyTaskChannel.FindNextTask` для поиска задачи с типом `type::documentation`. + Если задача найдена, изменить ее статус на `status::in-progress`. - - Если задача (`WorkOrder`) найдена: - - - Вызвать `MyTaskChannel.UpdateTaskStatus(IssueID={WorkOrder.ID}, OldStatus='status::pending', NewStatus='status::in-progress')`. - - - - - - Загрузить `tech_spec/PROJECT_MANIFEST.xml` в `original_manifest`. - Получить список всех файлов `*.kt` в проекте. - Сообщить о завершении сбора данных. - - - - Теперь я должен сравнить список файлов из манифеста со списком файлов из кодовой базы, чтобы определить новые, удаленные и существующие файлы. - Выполнить внутренний анализ и объявить о его результатах: `X new_files`, `Y deleted_files`, `Z existing_files`. - - - - Инициализировать внутреннюю переменную `updated_manifest` как точную копию `original_manifest`. Все последующие изменения будут применяться к ней. - - - - Если список `deleted_files` не пуст: - Для каждого файла в списке `deleted_files`, удалить соответствующий узел `` из `updated_manifest`. - Сообщить о завершении обработки удаленных файлов. - - - - Теперь я начну итерацию по всем новым и существующим файлам. Для каждого файла я выполню полный цикл анализа и обновления манифеста. - Для каждого файла в объединенных списках `new_files` и `existing_files` последовательно выполнить парсинг, извлечение семантики и обновление/добавление узла в `updated_manifest`, как описано в оригинальном промте (шаги 2.2.a - 2.2.h). - Сообщить о завершении обработки всех новых и существующих файлов. - - - - - **ЕСЛИ** `updated_manifest` отличается от `original_manifest`: - - a. Сохранить `updated_manifest` в файл `tech_spec/PROJECT_MANIFEST.xml`. - b. Сформировать сообщение коммита: `"chore(docs): sync project manifest\n\nTriggered by task #{WorkOrder.ID}."` - c. Вызвать `MyTaskChannel.CommitManifestChanges(CommitMessage=...)`. - d. Вызвать `MyTaskChannel.AddComment(IssueID={WorkOrder.ID}, CommentBody='Synchronization complete. Manifest updated and committed.')` - - **ИНАЧЕ:** - - a. Вызвать `MyTaskChannel.AddComment(IssueID={WorkOrder.ID}, CommentBody='Synchronization check complete. No changes detected.')` - - - - - Вызвать `MyTaskChannel.UpdateTaskStatus(IssueID={WorkOrder.ID}, OldStatus='status::in-progress', NewStatus='status::completed')`. - - + + Запустить инструмент синхронизации и получить отчет о его работе. + Сформировать список всех `.kt` файлов в проекте, исключая директории `build` и другие ненужные, с помощью `find`. + + Выполнить `Shell` команду: + `python3 extract_semantics.py --protocol agent_promts/protocols/semantic_enrichment_protocol.xml --manifest-path tech_spec/PROJECT_MANIFEST.xml --update-in-place [file_list]` + + Сохранить JSON-вывод скрипта в переменную `sync_report`. - - Собрать и отправить метрики через `MyMetricsSink`. + + + На основе отчета от инструмента, зафиксировать изменения и завершить задачу. + Проанализировать `sync_report`. Если в `changes` есть изменения (`nodes_added > 0` и т.д.): + + a. Сформировать сообщение коммита на основе статистики из `sync_report`. + b. Вызвать `MyTaskChannel.CommitChanges`. + c. Добавить в задачу комментарий об успешном обновлении манифеста. + + В противном случае (изменений нет): + + a. Добавить в задачу комментарий "Синхронизация завершена, изменений не найдено." + + Закрыть задачу, изменив ее статус на `status::completed`, и отправить метрики. \ No newline at end of file diff --git a/app/src/test/java/com/homebox/lens/ui/screen/itemedit/ItemEditViewModelTest.kt b/app/src/test/java/com/homebox/lens/ui/screen/itemedit/ItemEditViewModelTest.kt index f032c9a..c3c1c1a 100644 --- a/app/src/test/java/com/homebox/lens/ui/screen/itemedit/ItemEditViewModelTest.kt +++ b/app/src/test/java/com/homebox/lens/ui/screen/itemedit/ItemEditViewModelTest.kt @@ -1,3 +1,6 @@ +// [PACKAGE] com.homebox.lens.ui.screen.itemedit +// [FILE] ItemEditViewModelTest.kt +// [SEMANTICS] ui, viewmodel, testing package com.homebox.lens.ui.screen.itemedit @@ -123,4 +126,4 @@ class ItemEditViewModelTest { assertEquals("Updated Item", uiState.item?.name) assertEquals(4, uiState.item?.quantity) } -} +} \ No newline at end of file diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 2acc404..2cf0e88 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -1,3 +1,4 @@ +// [PACKAGE] buildsrc.dependencies // [FILE] Dependencies.kt // [SEMANTICS] build, dependencies diff --git a/data/semantic-ktlint-rules/src/androidTest/java/com/busya/ktlint/rules/ExampleInstrumentedTest.kt b/data/semantic-ktlint-rules/src/androidTest/java/com/busya/ktlint/rules/ExampleInstrumentedTest.kt index 481126d..05824fa 100644 --- a/data/semantic-ktlint-rules/src/androidTest/java/com/busya/ktlint/rules/ExampleInstrumentedTest.kt +++ b/data/semantic-ktlint-rules/src/androidTest/java/com/busya/ktlint/rules/ExampleInstrumentedTest.kt @@ -1,3 +1,7 @@ +// [PACKAGE] com.busya.ktlint.rules +// [FILE] ExampleInstrumentedTest.kt +// [SEMANTICS] testing, android, ktlint, rules + package com.busya.ktlint.rules import androidx.test.platform.app.InstrumentationRegistry diff --git a/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/CustomRuleSetProvider.kt b/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/CustomRuleSetProvider.kt index a13e70d..54df2a2 100644 --- a/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/CustomRuleSetProvider.kt +++ b/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/CustomRuleSetProvider.kt @@ -1,4 +1,6 @@ -// Путь: data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/CustomRuleSetProvider.kt +// [PACKAGE] com.busya.ktlint.rules +// [FILE] CustomRuleSetProvider.kt +// [SEMANTICS] ktlint, rules, provider package com.busya.ktlint.rules import com.pinterest.ktlint.rule.engine.core.api.RuleProvider diff --git a/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/FileHeaderRule.kt b/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/FileHeaderRule.kt index 04b2368..5769ad2 100644 --- a/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/FileHeaderRule.kt +++ b/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/FileHeaderRule.kt @@ -1,4 +1,6 @@ -// Путь: data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/FileHeaderRule.kt +// [PACKAGE] com.busya.ktlint.rules +// [FILE] FileHeaderRule.kt +// [SEMANTICS] ktlint, rules, file_header package com.busya.ktlint.rules import com.pinterest.ktlint.rule.engine.core.api.ElementType diff --git a/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/MandatoryEntityDeclarationRule.kt b/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/MandatoryEntityDeclarationRule.kt index fda8012..909f315 100644 --- a/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/MandatoryEntityDeclarationRule.kt +++ b/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/MandatoryEntityDeclarationRule.kt @@ -1,4 +1,6 @@ -// Путь: data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/MandatoryEntityDeclarationRule.kt +// [PACKAGE] com.busya.ktlint.rules +// [FILE] MandatoryEntityDeclarationRule.kt +// [SEMANTICS] ktlint, rules, entity_declaration package com.busya.ktlint.rules import com.pinterest.ktlint.rule.engine.core.api.ElementType diff --git a/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/NoStrayCommentsRule.kt b/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/NoStrayCommentsRule.kt index 5fb9d52..e427807 100644 --- a/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/NoStrayCommentsRule.kt +++ b/data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/NoStrayCommentsRule.kt @@ -1,4 +1,6 @@ -// Путь: data/semantic-ktlint-rules/src/main/java/com/busya/ktlint/rules/NoStrayCommentsRule.kt +// [PACKAGE] com.busya.ktlint.rules +// [FILE] NoStrayCommentsRule.kt +// [SEMANTICS] ktlint, rules, comments package com.busya.ktlint.rules import com.pinterest.ktlint.rule.engine.core.api.ElementType diff --git a/data/semantic-ktlint-rules/src/test/java/com/busya/ktlint/rules/ExampleUnitTest.kt b/data/semantic-ktlint-rules/src/test/java/com/busya/ktlint/rules/ExampleUnitTest.kt index ba90d43..df657d1 100644 --- a/data/semantic-ktlint-rules/src/test/java/com/busya/ktlint/rules/ExampleUnitTest.kt +++ b/data/semantic-ktlint-rules/src/test/java/com/busya/ktlint/rules/ExampleUnitTest.kt @@ -1,3 +1,7 @@ +// [PACKAGE] com.busya.ktlint.rules +// [FILE] ExampleUnitTest.kt +// [SEMANTICS] testing, ktlint, rules + package com.busya.ktlint.rules import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule diff --git a/extract_semantics.py b/extract_semantics.py new file mode 100644 index 0000000..8257328 --- /dev/null +++ b/extract_semantics.py @@ -0,0 +1,501 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# [PACKAGE] tools.semantic_parser +# [FILE] extract_semantics.py +# [SEMANTICS] cli, parser, xml, json, file_io, design_by_contract, structured_logging, protocol_resolver, graphrag, validation, manifest_synchronization + +# [AI_NOTE]: Этот скрипт является эталонной реализацией всех четырех ключевых принципов +# семантического обогащения. Он не только проверяет код на соответствие этим правилам, +# но и сам написан с их неукоснительным соблюдением. +# Версия 2.0 добавляет функциональность синхронизации манифеста. + +# [IMPORTS] +import sys +import re +import json +import argparse +import os +import logging +import xml.etree.ElementTree as ET +from typing import List, Dict, Any, Optional, Set +# [END_IMPORTS] + + +# [ENTITY: Class('StructuredFormatter')] +# [RELATION: Class('StructuredFormatter')] -> [INHERITS_FROM] -> [Class('logging.Formatter')] +class StructuredFormatter(logging.Formatter): + """ + @summary Форматтер для логов, реализующий стандарт AIFriendlyLogging. + @invariant Каждый лог, отформатированный этим классом, будет иметь структуру "[LEVEL][ANCHOR][STATE] message". + @sideeffect Отсутствуют. + """ + def format(self, record: logging.LogRecord) -> str: + assert record.msg is not None, "Сообщение лога не может быть None." + record.msg = f"[{record.levelname.upper()}]{record.msg}" + result = super().format(record) + assert result.startswith(f"[{record.levelname.upper()}]"), "Постусловие нарушено: лог не начинается с уровня." + return result +# [END_ENTITY: Class('StructuredFormatter')] + + +# [ENTITY: Class('SemanticProtocol')] +# [RELATION: Class('SemanticProtocol')] -> [DEPENDS_ON] -> [Module('xml.etree.ElementTree')] +class SemanticProtocol: + """ + @summary Загружает, разрешает и предоставляет доступ к правилам из протокола. + @description Этот класс действует как 'резолвер протоколов', рекурсивно обрабатывая + теги и объединяя правила из нескольких файлов в единый набор. + @invariant Экземпляр класса всегда содержит полный, объединенный набор правил. + @sideeffect Читает несколько файлов с диска при инициализации. + """ + def __init__(self, main_protocol_path: str): + logger.debug("[DEBUG][ENTRYPOINT][initializing_protocol] Инициализация протокола из главного файла: '%s'", main_protocol_path) + if not os.path.exists(main_protocol_path): + raise FileNotFoundError(f"Главный файл протокола не найден: {main_protocol_path}") + + self.processed_paths: Set[str] = set() + self.all_rule_nodes: List[ET.Element] = [] + self._resolve_and_load(main_protocol_path) + + self.rules = self._parse_all_rules() + logger.info("[INFO][ACTION][resolution_complete] Разрешение протокола завершено. Всего загружено правил: %d", len(self.rules)) + + def _resolve_and_load(self, file_path: str): + abs_path = os.path.abspath(file_path) + if abs_path in self.processed_paths: + return + + logger.info("[INFO][ACTION][resolving_includes] Обработка файла протокола: %s", abs_path) + self.processed_paths.add(abs_path) + + try: + tree = ET.parse(abs_path) + root = tree.getroot() + except ET.ParseError as e: + logger.error("[ERROR][ACTION][parsing_failed] Ошибка парсинга XML в файле %s: %s", abs_path, e) + return + + self.all_rule_nodes.extend(root.findall(".//Rule")) + + base_dir = os.path.dirname(abs_path) + for include_node in root.findall(".//INCLUDE"): + relative_path = include_node.get("from") + if relative_path and relative_path.lower().endswith('.xml'): + included_path = os.path.join(base_dir, relative_path) + self._resolve_and_load(included_path) + + def _parse_all_rules(self) -> Dict[str, Dict[str, Any]]: + rules_dict = {} + for rule_node in self.all_rule_nodes: + rule_id = rule_node.get('id') + if not rule_id: continue + definition_node = rule_node.find("Definition") + rules_dict[rule_id] = self._parse_definition(definition_node) + return rules_dict + + def _parse_definition(self, node: Optional[ET.Element]) -> Optional[Dict[str, Any]]: + if node is None: return None + def_type = node.get("type") + if def_type in ("regex", "dynamic_regex", "negative_regex"): + return {"type": def_type, "pattern": node.findtext("Pattern", "")} + if def_type == "paired_regex": + return {"type": def_type, "start": node.findtext("Pattern[@name='start']", ""), "end": node.findtext("Pattern[@name='end']", "")} + if def_type == "multi_check": + checks = [] + for check_node in node.findall(".//Check"): + check_data = check_node.attrib + check_data['failure_message'] = check_node.findtext("FailureMessage", "") + if check_data.get('type') == 'block_order': + check_data['preceding_pattern'] = check_node.findtext("PrecedingBlockPattern", "") + check_data['following_pattern'] = check_node.findtext("FollowingBlockPattern", "") + elif check_data.get('type') == 'kdoc_validation': + check_data['for_function'] = {t.get('name'): t.get('condition') for t in check_node.findall(".//RequiredTagsForFunction/Tag")} + check_data['for_class'] = {t.get('name'): t.get('condition') for t in check_node.findall(".//RequiredTagsForClass/Tag")} + elif check_data.get('type') == 'contract_enforcement': + condition_node = check_node.find("Condition") + check_data['kdoc_tag'] = condition_node.get('kdoc_tag') + check_data['code_must_contain'] = condition_node.get('code_must_contain') + elif check_data.get('type') == 'entity_type_validation': + check_data['valid_types'] = {t.text for t in check_node.findall(".//ValidEntityTypes/Type")} + elif check_data.get('type') == 'relation_validation': + check_data['triplet_pattern'] = check_node.findtext("TripletPattern", "") + check_data['valid_relations'] = {t.text for t in check_node.findall(".//ValidRelationTypes/Type")} + else: + check_data['pattern'] = check_node.findtext("Pattern", "") + checks.append(check_data) + return {"type": def_type, "checks": checks} + return None + + def get_rule(self, rule_id: str) -> Optional[Dict[str, Any]]: + return self.rules.get(rule_id) +# [END_ENTITY: Class('SemanticProtocol')] + + +# [ENTITY: Class('CodeValidator')] +# [RELATION: Class('CodeValidator')] -> [DEPENDS_ON] -> [Class('SemanticProtocol')] +class CodeValidator: + """ + @summary Применяет правила из протокола к содержимому файла для поиска ошибок. + @invariant Всегда работает с валидным и загруженным экземпляром SemanticProtocol. + """ + def __init__(self, protocol: SemanticProtocol): + self.protocol = protocol + + def validate(self, file_path: str, content: str, entity_blocks: List[str]) -> List[str]: + errors = [] + rules = self.protocol.rules + + if "AIFriendlyLogging" in rules: + errors.extend(self._validate_logging(file_path, content, rules["AIFriendlyLogging"])) + + if "DesignByContract" in rules or "GraphRAG" in rules: + for entity_content in entity_blocks: + if "DesignByContract" in rules: + errors.extend(self._validate_entity_dbc(entity_content, rules["DesignByContract"])) + if "GraphRAG" in rules: + errors.extend(self._validate_entity_graphrag(entity_content, rules["GraphRAG"])) + + return list(set(errors)) + + def _validate_logging(self, file_path: str, content: str, rule: Dict) -> List[str]: + errors = [] + if rule.get('type') != 'multi_check': return [] + for check in rule['checks']: + if check.get('type') == 'negative_regex_in_path' and check.get('path_contains') in file_path and re.search(check['pattern'], content): + errors.append(check['failure_message']) + elif check.get('type') == 'negative_regex' and re.search(check['pattern'], content): + errors.append(check['failure_message']) + elif check.get('type') == 'positive_regex_on_match': + for line in content.splitlines(): + if re.search(check['trigger'], line) and not re.search(check['pattern'], line): + errors.append(f"{check['failure_message']} [Строка: '{line.strip()}']") + return errors + + def _validate_entity_dbc(self, entity_content: str, rule: Dict) -> List[str]: + errors = [] + if rule.get('type') != 'multi_check': return [] + kdoc_match = re.search(r"(\/\*\*[\s\S]*?\*\/)", entity_content) + kdoc = kdoc_match.group(1) if kdoc_match else "" + signature_match = re.search(r"\s*(public\s+|private\s+|internal\s+)?(class|interface|fun|object)\s+\w+", entity_content) + is_public = not (signature_match and signature_match.group(1) and 'private' in signature_match.group(1)) if signature_match else False + + for check in rule['checks']: + if not is_public and check.get('type') != 'block_order': continue # Проверки контрактов только для public + if check.get('type') == 'kdoc_validation': + is_class = bool(re.search(r"\s*(class|interface|object)", entity_content)) + if is_class: + for tag, _ in check['for_class'].items(): + if tag not in kdoc: errors.append(f"{check['failure_message']} ({tag})") + else: # is_function + has_params = bool(re.search(r"fun\s+\w+\s*\((.|\s)*\S(.|\s)*\)", entity_content)) + returns_value = not bool(re.search(r"fun\s+\w+\(.*\)\s*:\s*Unit", entity_content) or not re.search(r"fun\s+\w+\(.*\)\s*:", entity_content)) + for tag, cond in check['for_function'].items(): + if tag not in kdoc and (not cond or (cond == 'has_parameters' and has_params) or (cond == 'returns_value' and returns_value)): + errors.append(f"{check['failure_message']} ({tag})") + elif check.get('type') == 'contract_enforcement' and check['kdoc_tag'] in kdoc and not re.search(check['code_must_contain'], entity_content): + errors.append(check['failure_message']) + return errors + + def _validate_entity_graphrag(self, entity_content: str, rule: Dict) -> List[str]: + errors = [] + if rule.get('type') != 'multi_check': return [] + markup_block_match = re.search(r"^([\s\S]*?)(\/\*\*|class|interface|fun|object)", entity_content) + markup_block = markup_block_match.group(1) if markup_block_match else "" + + for check in rule['checks']: + if check.get('type') == 'block_order' and "/**" in markup_block: + errors.append(check['failure_message']) + elif check.get('type') == 'entity_type_validation': + entity_match = re.search(r"//\s*\[ENTITY:\s*(?P\w+)\(‘(?P.*?)’\)\]", markup_block) + if entity_match and entity_match.group('type') not in check['valid_types']: + errors.append(f"{check['failure_message']} Найдено: ‘{entity_match.group('type')}’.") + elif check.get('type') == 'relation_validation': + for line in re.findall(r"//\s*\[RELATION:.*\]", markup_block): + match = re.match(check['triplet_pattern'], line) + if not match: + errors.append(f"{check['failure_message']} (неверный формат). Строка: ‘{line.strip()}’") + elif match.group('relation_type') not in check['valid_relations']: + errors.append(f"{check['failure_message']} Найдено: ‘[{match.group('relation_type')}]’.") + elif check.get('type') == 'markup_cohesion': + for line in markup_block.strip().split('\n'): + if line.strip() and not line.strip().startswith('//'): + errors.append(check['failure_message']); break + return errors +# [END_ENTITY: Class('CodeValidator')] + + +# [ENTITY: Class('SemanticParser')] +# [RELATION: Class('SemanticParser')] -> [DEPENDS_ON] -> [Class('SemanticProtocol')] +# [RELATION: Class('SemanticParser')] -> [CREATES_INSTANCE_OF] -> [Class('CodeValidator')] +class SemanticParser: + """ + @summary Оркестрирует процесс валидации и парсинга исходных файлов. + @invariant Всегда работает с валидным и загруженным экземпляром SemanticProtocol. + @sideeffect Читает содержимое файлов, переданных для парсинга. + """ + def __init__(self, protocol: SemanticProtocol): + assert isinstance(protocol, SemanticProtocol), "Объект protocol должен быть экземпляром SemanticProtocol." + self.protocol = protocol + self.validator = CodeValidator(protocol) + + def parse_file(self, file_path: str) -> Dict[str, Any]: + logger.info("[INFO][ENTRYPOINT][parsing_file] Начало парсинга файла: '%s'", file_path) + try: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + except Exception as e: + return {"file_path": file_path, "status": "error", "error_message": f"Не удалось прочитать файл: {e}"} + + entity_rule = self.protocol.get_rule("EntityContainerization") + entity_blocks = re.findall(entity_rule['start'] + r'[\s\S]*?' + entity_rule['end'], content, re.DOTALL) if entity_rule else [] + + validation_errors = self.validator.validate(file_path, content, entity_blocks) + + header_rule = self.protocol.get_rule("FileHeaderIntegrity") + if not re.search(header_rule['pattern'], content) if header_rule else None: + msg = "Нарушение целостности заголовка (правило FileHeaderIntegrity)." + if msg not in validation_errors: validation_errors.append(msg) + + if validation_errors: + logger.warn("[WARN][ACTION][validation_failed] Файл %s не прошел валидацию: %s", file_path, " | ".join(validation_errors)) + return {"file_path": file_path, "status": "error", "error_message": " | ".join(validation_errors)} + + header_match = re.search(header_rule['pattern'], content) + header_data = header_match.groupdict() + file_info = { + "file_path": file_path, "status": "success", + "header": {"package": header_data.get('package','').strip(), "file_name": header_data.get('file','').strip(), "semantics_tags": [t.strip() for t in header_data.get('semantics','').split(',')]}, + "entities": self._extract_entities(content) + } + + logger.info("[INFO][POSTCONDITION][parsing_complete] Парсинг файла завершен. Найдено сущностей: %d", len(file_info["entities"])) + return file_info + + def _extract_entities(self, content: str) -> List[Dict[str, Any]]: + entity_rule = self.protocol.get_rule("EntityContainerization") + if not entity_rule: return [] + entities = [] + for match in re.finditer(entity_rule['start'] + r'(?P.*?)' + entity_rule['end'], content, re.DOTALL): + data = match.groupdict() + kdoc = self._parse_kdoc(data.get('body', '')) + e_type, e_name = data.get('type', 'N/A'), data.get('name', 'N/A') + type_snake = re.sub(r'(? Dict[str, Any]: + summary_match = re.search(r"@summary\s*(.*)", body) + summary = summary_match.group(1).strip() if summary_match else "" + desc_match = re.search(r"@description\s*(.*)", body, re.DOTALL) + desc = "" + if desc_match: + lines = [re.sub(r"^\s*\*\s?", "", l).strip() for l in desc_match.group(1).strip().split('\n')] + desc = " ".join(lines) + relations = [m.groupdict() for m in re.finditer(r"[RELATION:\s*(?P\w+)\s*target_id='(?P.*?)']", body)] + return {"summary": summary, "description": desc, "relations": relations} +# [END_ENTITY: Class('SemanticParser')] + + +# [ENTITY: Class('ManifestSynchronizer')] +# [RELATION: Class('ManifestSynchronizer')] -> [DEPENDS_ON] -> [Module('xml.etree.ElementTree')] +# [RELATION: Class('ManifestSynchronizer')] -> [MODIFIES_STATE_OF] -> [DataStructure('PROJECT_MANIFEST.xml')] +class ManifestSynchronizer: + """ + @summary Управляет чтением, сравнением и обновлением PROJECT_MANIFEST.xml. + @invariant Экземпляр класса всегда работает с корректно загруженным XML-деревом. + @sideeffect Читает и может перезаписывать файл манифеста на диске. + """ + def __init__(self, manifest_path: str): + """ + @param manifest_path: Путь к файлу PROJECT_MANIFEST.xml. + @sideeffect Читает и парсит XML-файл. Вызывает исключение, если файл не найден или поврежден. + """ + require(os.path.exists(manifest_path), f"Файл манифеста не найден: {manifest_path}") + logger.info("[INFO][ENTRYPOINT][manifest_loading] Загрузка манифеста: %s", manifest_path) + self.manifest_path = manifest_path + try: + self.tree = ET.parse(manifest_path) + self.root = self.tree.getroot() + self.graph_node = self.root.find("PROJECT_GRAPH") + if self.graph_node is None: + raise ValueError("В манифесте отсутствует тег ") + except (ET.ParseError, ValueError) as e: + logger.error("[ERROR][ACTION][manifest_parsing_failed] Ошибка парсинга манифеста: %s", e) + raise ValueError(f"Ошибка парсинга манифеста: {e}") + + def synchronize(self, parsed_code_data: List[Dict[str, Any]]) -> Dict[str, int]: + """ + @summary Синхронизирует состояние манифеста с состоянием кодовой базы. + @param parsed_code_data: Список словарей, представляющих состояние файлов, от SemanticParser. + @return Словарь со статистикой изменений. + @sideeffect Модифицирует внутреннее XML-дерево. + """ + stats = {"nodes_added": 0, "nodes_updated": 0, "nodes_removed": 0} + + all_code_node_ids = { + entity["node_id"] + for file_data in parsed_code_data if file_data["status"] == "success" + for entity in file_data["entities"] + } + + manifest_nodes_map = {node.get("id"): node for node in self.graph_node.findall("NODE")} + manifest_node_ids = set(manifest_nodes_map.keys()) + + # Удаление узлов, которых больше нет в коде + nodes_to_remove = manifest_node_ids - all_code_node_ids + for node_id in nodes_to_remove: + logger.debug("[DEBUG][ACTION][removing_node] Удаление устаревшего узла: %s", node_id) + self.graph_node.remove(manifest_nodes_map[node_id]) + stats["nodes_removed"] += 1 + + # Добавление и обновление узлов + for file_data in parsed_code_data: + if file_data["status"] != "success": + continue + for entity in file_data["entities"]: + node_id = entity["node_id"] + existing_node = manifest_nodes_map.get(node_id) + + if existing_node is None: + logger.debug("[DEBUG][ACTION][adding_node] Добавление нового узла: %s", node_id) + new_node = ET.SubElement(self.graph_node, "NODE", id=node_id) + self._update_node_attributes(new_node, entity, file_data) + stats["nodes_added"] += 1 + else: + if self._is_update_needed(existing_node, entity, file_data): + logger.debug("[DEBUG][ACTION][updating_node] Обновление узла: %s", node_id) + self._update_node_attributes(existing_node, entity, file_data) + stats["nodes_updated"] += 1 + + logger.info("[INFO][POSTCONDITION][synchronization_complete] Синхронизация завершена. Статистика: %s", stats) + return stats + + def _update_node_attributes(self, node: ET.Element, entity: Dict, file_data: Dict): + node.set("type", entity["entity_type"]) + node.set("name", entity["entity_name"]) + node.set("file_path", file_data["file_path"]) + node.set("package", file_data["header"]["package"]) + + # Очистка и добавление дочерних тегов + for child in list(node): + node.remove(child) + + ET.SubElement(node, "SUMMARY").text = entity["summary"] + ET.SubElement(node, "DESCRIPTION").text = entity["description"] + tags_node = ET.SubElement(node, "SEMANTICS_TAGS") + tags_node.text = ", ".join(file_data["header"]["semantics_tags"]) + + relations_node = ET.SubElement(node, "RELATIONS") + for rel in entity["relations"]: + ET.SubElement(relations_node, "RELATION", type=rel["type"], target_id=rel["target"]) + + def _is_update_needed(self, node: ET.Element, entity: Dict, file_data: Dict) -> bool: + # Простая проверка по нескольким ключевым полям + if node.get("type") != entity["entity_type"] or node.get("name") != entity["entity_name"]: + return True + summary_node = node.find("SUMMARY") + if summary_node is None or summary_node.text != entity["summary"]: + return True + return False + + def write_xml(self): + """ + @summary Записывает измененное XML-дерево обратно в файл. + @sideeffect Перезаписывает файл манифеста на диске. + """ + require(self.tree is not None, "XML-дерево не было инициализировано.") + logger.info("[INFO][ACTION][writing_manifest] Запись изменений в файл манифеста: %s", self.manifest_path) + ET.indent(self.tree, space=" ") + self.tree.write(self.manifest_path, encoding="utf-8", xml_declaration=True) +# [END_ENTITY: Class('ManifestSynchronizer')] + + +# [ENTITY: Function('require')] +def require(condition: bool, message: str): + """ + @summary Проверяет предусловие и вызывает ValueError, если оно ложно. + @param condition: Условие для проверки. + @param message: Сообщение об ошибке. + @sideeffect Вызывает исключение при ложном условии. + """ + if not condition: + raise ValueError(message) +# [END_ENTITY: Function('require')] + + +# [ENTITY: Function('main')] +# [RELATION: Function('main')] -> [CREATES_INSTANCE_OF] -> [Class('SemanticProtocol')] +# [RELATION: Function('main')] -> [CREATES_INSTANCE_OF] -> [Class('SemanticParser')] +# [RELATION: Function('main')] -> [CREATES_INSTANCE_OF] -> [Class('ManifestSynchronizer')] +def main(): + """ + @summary Главная точка входа в приложение. + @description Управляет жизненным циклом: парсинг аргументов, настройка логирования, + запуск парсинга файлов и синхронизации манифеста. + @sideeffect Читает аргументы командной строки, выводит результат в stdout/stderr. + """ + parser = argparse.ArgumentParser(description="Парсит .kt файлы и синхронизирует манифест проекта.") + parser.add_argument('files', nargs='+', help="Список .kt файлов для обработки.") + parser.add_argument('--protocol', required=True, help="Путь к главному файлу протокола.") + parser.add_argument('--manifest-path', required=True, help="Путь к файлу PROJECT_MANIFEST.xml.") + parser.add_argument('--update-in-place', action='store_true', help="Если указано, перезаписывает файл манифеста.") + parser.add_argument('--log-level', default='INFO', choices=['DEBUG', 'INFO', 'WARN', 'ERROR'], help="Уровень логирования.") + args = parser.parse_args() + + logger.setLevel(args.log_level) + handler = logging.StreamHandler(sys.stderr) + handler.setFormatter(StructuredFormatter()) + logger.addHandler(handler) + logger.info("[INFO][INITIALIZATION][configuring_logger] Логгер настроен. Уровень: %s", args.log_level) + + output_report = { + "status": "failure", + "manifest_path": args.manifest_path, + "files_scanned": len(args.files), + "files_with_errors": 0, + "changes": {} + } + + try: + protocol = SemanticProtocol(args.protocol) + parser_instance = SemanticParser(protocol) + + parsed_results = [parser_instance.parse_file(f) for f in args.files] + output_report["files_with_errors"] = sum(1 for r in parsed_results if r["status"] == "error") + + synchronizer = ManifestSynchronizer(args.manifest_path) + change_stats = synchronizer.synchronize(parsed_results) + output_report["changes"] = change_stats + + if args.update_in_place: + if sum(change_stats.values()) > 0: + synchronizer.write_xml() + logger.info("[INFO][ACTION][manifest_updated] Манифест был успешно обновлен.") + else: + logger.info("[INFO][ACTION][manifest_not_updated] Изменений не было, манифест не перезаписан.") + + output_report["status"] = "success" + + except (FileNotFoundError, ValueError, ET.ParseError) as e: + logger.critical("[FATAL][EXECUTION][critical_error] Критическая ошибка: %s", e, exc_info=True) + output_report["error_message"] = str(e) + + finally: + print(json.dumps(output_report, indent=2, ensure_ascii=False)) + if output_report["status"] == "failure": + sys.exit(1) + +# [END_ENTITY: Function('main')] + +# [CONTRACT] +if __name__ == "__main__": + logger = logging.getLogger(__name__) + main() +# [END_CONTRACT] + +# [END_FILE_extract_semantics.py]