Compare commits
2 Commits
e173556bf7
...
30ef449756
| Author | SHA1 | Date | |
|---|---|---|---|
| 30ef449756 | |||
| c5ee179e71 |
@@ -1,85 +1,26 @@
|
|||||||
<GITEA_ISSUE_DRIVEN_PROTOCOL>
|
<IMPLEMENTATION name="GiteaIssueTaskSource">
|
||||||
<META>
|
<IMPLEMENTS_INTERFACE type="TaskSource"/>
|
||||||
<PURPOSE>Определить единый, отказоустойчивый и полностью автоматизированный протокол для межагентной коммуникации, основанный на использовании высокоуровневого клиента 'gitea-client.zsh'.</PURPOSE>
|
<USES_PROTOCOL name="GiteaIssueDrivenProtocol"/>
|
||||||
<VERSION>4.0</VERSION>
|
|
||||||
</META>
|
|
||||||
|
|
||||||
<CORE_PRINCIPLES>
|
<DESCRIPTION>
|
||||||
<PRINCIPLE name="Abstraction_Is_Mandatory">
|
Реализует канал получения задач через поиск открытых issues в Gitea,
|
||||||
<DESCRIPTION>**КЛЮЧЕВОЕ ИЗМЕНЕНИЕ:** Все взаимодействия с Gitea **ОБЯЗАНЫ** осуществляться исключительно через `gitea-client.zsh`. Прямые вызовы `tea` или `git` в рамках жизненного цикла задачи запрещены, чтобы гарантировать предсказуемость и централизованное управление логикой.</DESCRIPTION>
|
используя `gitea-client.zsh`.
|
||||||
</PRINCIPLE>
|
</DESCRIPTION>
|
||||||
<PRINCIPLE name="Automated_Context_Discovery">
|
|
||||||
<DESCRIPTION>Клиент `gitea-client.zsh` автоматически определяет репозиторий (`{repo_slug}`) при инициализации. Агентам не нужно управлять этим состоянием. Роль (`{role_name}`) передается как первый аргумент при каждом вызове.</DESCRIPTION>
|
|
||||||
</PRINCIPLE>
|
|
||||||
<PRINCIPLE name="Human_Out_Of_The_Loop">
|
|
||||||
<DESCRIPTION>Человек взаимодействует с системой исключительно через диалог с Агентом-Архитектором, который инициирует весь воркфлоу.</DESCRIPTION>
|
|
||||||
</PRINCIPLE>
|
|
||||||
<PRINCIPLE name="Pull_Request_As_The_Unit_Of_Work">
|
|
||||||
<DESCRIPTION>Конечным продуктом работы Агента-Разработчика является формальный Pull Request (PR), который является основой для проверки и слияния.</DESCRIPTION>
|
|
||||||
</PRINCIPLE>
|
|
||||||
</CORE_PRINCIPLES>
|
|
||||||
|
|
||||||
<CLIENT_API name="gitea-client.zsh">
|
<METHOD_IMPLEMENTATION name="GetNextPendingTask">
|
||||||
<SYNTAX>`./gitea-client.zsh {role_name} {command} [options]`</SYNTAX>
|
<INPUT>RoleName</INPUT>
|
||||||
<COMMAND name="create-task">
|
<ACTION>
|
||||||
<SIGNATURE>`create-task --title "..." --body "..." --assignee "..." --labels "..."`</SIGNATURE>
|
Выполнить команду `./gitea-client.zsh {RoleName} find-tasks --type "type::development"`
|
||||||
<PURPOSE>Создание новой задачи в Gitea.</PURPOSE>
|
для поиска доступных задач для указанной роли.
|
||||||
</COMMAND>
|
</ACTION>
|
||||||
<COMMAND name="find-tasks">
|
<ACTION>
|
||||||
<SIGNATURE>`find-tasks --type "{label_name}"`</SIGNATURE>
|
Если найдена одна или несколько задач, взять первую из списка.
|
||||||
<PURPOSE>Поиск открытых задач с нужным типом и статусом 'pending'.</PURPOSE>
|
</ACTION>
|
||||||
</COMMAND>
|
<ACTION>
|
||||||
<COMMAND name="update-task-status">
|
Извлечь содержимое задачи (WorkOrder) и вернуть его.
|
||||||
<SIGNATURE>`update-task-status --issue-id ID --old "{label}" --new "{label}"`</SIGNATURE>
|
</ACTION>
|
||||||
<PURPOSE>Атомарное изменение статуса задачи (например, с 'pending' на 'in-progress').</PURPOSE>
|
<ACTION>
|
||||||
</COMMAND>
|
Если задачи не найдены, вернуть `NULL`.
|
||||||
<COMMAND name="create-pr">
|
</ACTION>
|
||||||
<SIGNATURE>`create-pr --title "..." --body "..." --head "{branch}" --base "{target_branch}"`</SIGNATURE>
|
</METHOD_IMPLEMENTATION>
|
||||||
<PURPOSE>Создание Pull Request.</PURPOSE>
|
</IMPLEMENTATION>
|
||||||
</COMMAND>
|
|
||||||
<COMMAND name="merge-and-complete">
|
|
||||||
<SIGNATURE>`merge-and-complete --issue-id ID --pr-id ID --branch "{branch_to_delete}"`</SIGNATURE>
|
|
||||||
<PURPOSE>Атомарная операция: слияние PR, удаление ветки и закрытие связанной задачи.</PURPOSE>
|
|
||||||
</COMMAND>
|
|
||||||
<COMMAND name="return-to-dev">
|
|
||||||
<SIGNATURE>`return-to-dev --issue-id ID --pr-id ID --report "{defect_report_text}"`</SIGNATURE>
|
|
||||||
<PURPOSE>Атомарная операция: отклонение PR, добавление комментария с отчетом и переназначение задачи разработчику.</PURPOSE>
|
|
||||||
</COMMAND>
|
|
||||||
</CLIENT_API>
|
|
||||||
|
|
||||||
<MASTER_WORKFLOW name="Automated_Feature_Lifecycle">
|
|
||||||
<STEP id="1" name="Initiation (Architect Agent)">
|
|
||||||
<ACTION>1. Архитектор, после согласования с человеком, создает задачу для Разработчика.</ACTION>
|
|
||||||
<CLIENT_CALL>`./gitea-client.zsh agent-architect create-task --title "Реализовать модуль X" --body "..." --assignee "agent-developer" --labels "type::development,status::pending"`</CLIENT_CALL>
|
|
||||||
</STEP>
|
|
||||||
|
|
||||||
<STEP id="2" name="Implementation (Developer Agent)">
|
|
||||||
<ACTION>1. Разработчик находит назначенную ему задачу.</ACTION>
|
|
||||||
<CLIENT_CALL>`./gitea-client.zsh agent-developer find-tasks --type "type::development"`</CLIENT_CALL>
|
|
||||||
<ACTION>2. Берет задачу в работу.</ACTION>
|
|
||||||
<CLIENT_CALL>`./gitea-client.zsh agent-developer update-task-status --issue-id {issue-id} --old "status::pending" --new "status::in-progress"`</CLIENT_CALL>
|
|
||||||
<ACTION>3. После написания кода и локальных тестов создает Pull Request.</ACTION>
|
|
||||||
<CLIENT_CALL>`./gitea-client.zsh agent-developer create-pr --title "feat: Реализован модуль X" --body "Closes #{issue-id}" --head "feature/{issue-id}-module-x"`</CLIENT_CALL>
|
|
||||||
<ACTION>4. Создает задачу для QA-агента, передавая ему контекст (ID задачи и PR).</ACTION>
|
|
||||||
<CLIENT_CALL>`./gitea-client.zsh agent-developer create-task --title "QA: Проверить реализацию модуля X" --body "PR: #{pr-id}\nIssue: #{issue-id}" --assignee "agent-qa" --labels "type::quality-assurance,status::pending"`</CLIENT_CALL>
|
|
||||||
</STEP>
|
|
||||||
|
|
||||||
<STEP id="3" name="Verification_And_Merge (QA Agent)">
|
|
||||||
<ACTION>1. QA-Агент находит свою задачу.</ACTION>
|
|
||||||
<CLIENT_CALL>`./gitea-client.zsh agent-qa find-tasks --type "type::quality-assurance"`</CLIENT_CALL>
|
|
||||||
<ACTION>2. Берет задачу в работу.</ACTION>
|
|
||||||
<CLIENT_CALL>`./gitea-client.zsh agent-qa update-task-status --issue-id {qa-issue-id} --old "status::pending" --new "status::in-progress"`</CLIENT_CALL>
|
|
||||||
<ACTION>3. Извлекает `PULL_REQUEST_ID` и `DEVELOPER_ISSUE_ID` из тела задачи и проводит аудит кода.</ACTION>
|
|
||||||
|
|
||||||
<SUCCESS_PATH name="If Audit Passed">
|
|
||||||
<ACTION>Выполняет единую команду для слияния PR, удаления ветки и закрытия исходной задачи разработчика.</ACTION>
|
|
||||||
<CLIENT_CALL>`./gitea-client.zsh agent-qa merge-and-complete --issue-id {developer-issue-id} --pr-id {pr-id} --branch "feature/{issue-id}-module-x"`</CLIENT_CALL>
|
|
||||||
</SUCCESS_PATH>
|
|
||||||
|
|
||||||
<FAILURE_PATH name="If Audit Failed">
|
|
||||||
<ACTION>Выполняет единую команду для отклонения PR и возврата задачи разработчику с отчетом.</ACTION>
|
|
||||||
<CLIENT_CALL>`./gitea-client.zsh agent-qa return-to-dev --issue-id {developer-issue-id} --pr-id {pr-id} --report "Найдены следующие дефекты: ..."`</CLIENT_CALL>
|
|
||||||
</FAILURE_PATH>
|
|
||||||
</STEP>
|
|
||||||
</MASTER_WORKFLOW>
|
|
||||||
</GITEA_ISSUE_DRIVEN_PROTOCOL>
|
|
||||||
|
|||||||
17
agent_promts/implementations/xml_file_metrics_sink.xml
Normal file
17
agent_promts/implementations/xml_file_metrics_sink.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<IMPLEMENTATION name="XmlFileMetricsSink">
|
||||||
|
<IMPLEMENTS_INTERFACE type="MetricsSink"/>
|
||||||
|
|
||||||
|
<DESCRIPTION>
|
||||||
|
Реализует канал для метрик путем дозаписи в файл 'logs/metrics_log.xml'.
|
||||||
|
</DESCRIPTION>
|
||||||
|
|
||||||
|
<METHOD_IMPLEMENTATION name="Send">
|
||||||
|
<INPUT>MetricsBundle</INPUT>
|
||||||
|
<ACTION>
|
||||||
|
Сформировать XML-блок `<METRICS_ENTRY>` на основе `MetricsBundle`.
|
||||||
|
</ACTION>
|
||||||
|
<ACTION>
|
||||||
|
Добавить (append) сформированный блок в файл `/home/busya/dev/homebox_lens/logs/metrics_log.xml`.
|
||||||
|
</ACTION>
|
||||||
|
</METHOD_IMPLEMENTATION>
|
||||||
|
</IMPLEMENTATION>
|
||||||
7
agent_promts/interfaces/metrics_sink_interface.xml
Normal file
7
agent_promts/interfaces/metrics_sink_interface.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<!--
|
||||||
|
Абстрактный контракт для любого приемника метрик.
|
||||||
|
Он гарантирует, что у любого приемника будет метод Send для записи метрик.
|
||||||
|
-->
|
||||||
|
<INTERFACE name="MetricsSink">
|
||||||
|
<METHOD name="Send" accepts="MetricsBundle"/>
|
||||||
|
</INTERFACE>
|
||||||
85
agent_promts/protocols/gitea_protocol.xml
Normal file
85
agent_promts/protocols/gitea_protocol.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<GITEA_ISSUE_DRIVEN_PROTOCOL>
|
||||||
|
<META>
|
||||||
|
<PURPOSE>Определить единый, отказоустойчивый и полностью автоматизированный протокол для межагентной коммуникации, основанный на использовании высокоуровневого клиента 'gitea-client.zsh'.</PURPOSE>
|
||||||
|
<VERSION>4.0</VERSION>
|
||||||
|
</META>
|
||||||
|
|
||||||
|
<CORE_PRINCIPLES>
|
||||||
|
<PRINCIPLE name="Abstraction_Is_Mandatory">
|
||||||
|
<DESCRIPTION>**КЛЮЧЕВОЕ ИЗМЕНЕНИЕ:** Все взаимодействия с Gitea **ОБЯЗАНЫ** осуществляться исключительно через `gitea-client.zsh`. Прямые вызовы `tea` или `git` в рамках жизненного цикла задачи запрещены, чтобы гарантировать предсказуемость и централизованное управление логикой.</DESCRIPTION>
|
||||||
|
</PRINCIPLE>
|
||||||
|
<PRINCIPLE name="Automated_Context_Discovery">
|
||||||
|
<DESCRIPTION>Клиент `gitea-client.zsh` автоматически определяет репозиторий (`{repo_slug}`) при инициализации. Агентам не нужно управлять этим состоянием. Роль (`{role_name}`) передается как первый аргумент при каждом вызове.</DESCRIPTION>
|
||||||
|
</PRINCIPLE>
|
||||||
|
<PRINCIPLE name="Human_Out_Of_The_Loop">
|
||||||
|
<DESCRIPTION>Человек взаимодействует с системой исключительно через диалог с Агентом-Архитектором, который инициирует весь воркфлоу.</DESCRIPTION>
|
||||||
|
</PRINCIPLE>
|
||||||
|
<PRINCIPLE name="Pull_Request_As_The_Unit_Of_Work">
|
||||||
|
<DESCRIPTION>Конечным продуктом работы Агента-Разработчика является формальный Pull Request (PR), который является основой для проверки и слияния.</DESCRIPTION>
|
||||||
|
</PRINCIPLE>
|
||||||
|
</CORE_PRINCIPLES>
|
||||||
|
|
||||||
|
<CLIENT_API name="gitea-client.zsh">
|
||||||
|
<SYNTAX>`./gitea-client.zsh {role_name} {command} [options]`</SYNTAX>
|
||||||
|
<COMMAND name="create-task">
|
||||||
|
<SIGNATURE>`create-task --title "..." --body "..." --assignee "..." --labels "..."`</SIGNATURE>
|
||||||
|
<PURPOSE>Создание новой задачи в Gitea.</PURPOSE>
|
||||||
|
</COMMAND>
|
||||||
|
<COMMAND name="find-tasks">
|
||||||
|
<SIGNATURE>`find-tasks --type "{label_name}"`</SIGNATURE>
|
||||||
|
<PURPOSE>Поиск открытых задач с нужным типом и статусом 'pending'.</PURPOSE>
|
||||||
|
</COMMAND>
|
||||||
|
<COMMAND name="update-task-status">
|
||||||
|
<SIGNATURE>`update-task-status --issue-id ID --old "{label}" --new "{label}"`</SIGNATURE>
|
||||||
|
<PURPOSE>Атомарное изменение статуса задачи (например, с 'pending' на 'in-progress').</PURPOSE>
|
||||||
|
</COMMAND>
|
||||||
|
<COMMAND name="create-pr">
|
||||||
|
<SIGNATURE>`create-pr --title "..." --body "..." --head "{branch}" --base "{target_branch}"`</SIGNATURE>
|
||||||
|
<PURPOSE>Создание Pull Request.</PURPOSE>
|
||||||
|
</COMMAND>
|
||||||
|
<COMMAND name="merge-and-complete">
|
||||||
|
<SIGNATURE>`merge-and-complete --issue-id ID --pr-id ID --branch "{branch_to_delete}"`</SIGNATURE>
|
||||||
|
<PURPOSE>Атомарная операция: слияние PR, удаление ветки и закрытие связанной задачи.</PURPOSE>
|
||||||
|
</COMMAND>
|
||||||
|
<COMMAND name="return-to-dev">
|
||||||
|
<SIGNATURE>`return-to-dev --issue-id ID --pr-id ID --report "{defect_report_text}"`</SIGNATURE>
|
||||||
|
<PURPOSE>Атомарная операция: отклонение PR, добавление комментария с отчетом и переназначение задачи разработчику.</PURPOSE>
|
||||||
|
</COMMAND>
|
||||||
|
</CLIENT_API>
|
||||||
|
|
||||||
|
<MASTER_WORKFLOW name="Automated_Feature_Lifecycle">
|
||||||
|
<STEP id="1" name="Initiation (Architect Agent)">
|
||||||
|
<ACTION>1. Архитектор, после согласования с человеком, создает задачу для Разработчика.</ACTION>
|
||||||
|
<CLIENT_CALL>`./gitea-client.zsh agent-architect create-task --title "Реализовать модуль X" --body "..." --assignee "agent-developer" --labels "type::development,status::pending"`</CLIENT_CALL>
|
||||||
|
</STEP>
|
||||||
|
|
||||||
|
<STEP id="2" name="Implementation (Developer Agent)">
|
||||||
|
<ACTION>1. Разработчик находит назначенную ему задачу.</ACTION>
|
||||||
|
<CLIENT_CALL>`./gitea-client.zsh agent-developer find-tasks --type "type::development"`</CLIENT_CALL>
|
||||||
|
<ACTION>2. Берет задачу в работу.</ACTION>
|
||||||
|
<CLIENT_CALL>`./gitea-client.zsh agent-developer update-task-status --issue-id {issue-id} --old "status::pending" --new "status::in-progress"`</CLIENT_CALL>
|
||||||
|
<ACTION>3. После написания кода и локальных тестов создает Pull Request.</ACTION>
|
||||||
|
<CLIENT_CALL>`./gitea-client.zsh agent-developer create-pr --title "feat: Реализован модуль X" --body "Closes #{issue-id}" --head "feature/{issue-id}-module-x"`</CLIENT_CALL>
|
||||||
|
<ACTION>4. Создает задачу для QA-агента, передавая ему контекст (ID задачи и PR).</ACTION>
|
||||||
|
<CLIENT_CALL>`./gitea-client.zsh agent-developer create-task --title "QA: Проверить реализацию модуля X" --body "PR: #{pr-id}\nIssue: #{issue-id}" --assignee "agent-qa" --labels "type::quality-assurance,status::pending"`</CLIENT_CALL>
|
||||||
|
</STEP>
|
||||||
|
|
||||||
|
<STEP id="3" name="Verification_And_Merge (QA Agent)">
|
||||||
|
<ACTION>1. QA-Агент находит свою задачу.</ACTION>
|
||||||
|
<CLIENT_CALL>`./gitea-client.zsh agent-qa find-tasks --type "type::quality-assurance"`</CLIENT_CALL>
|
||||||
|
<ACTION>2. Берет задачу в работу.</ACTION>
|
||||||
|
<CLIENT_CALL>`./gitea-client.zsh agent-qa update-task-status --issue-id {qa-issue-id} --old "status::pending" --new "status::in-progress"`</CLIENT_CALL>
|
||||||
|
<ACTION>3. Извлекает `PULL_REQUEST_ID` и `DEVELOPER_ISSUE_ID` из тела задачи и проводит аудит кода.</ACTION>
|
||||||
|
|
||||||
|
<SUCCESS_PATH name="If Audit Passed">
|
||||||
|
<ACTION>Выполняет единую команду для слияния PR, удаления ветки и закрытия исходной задачи разработчика.</ACTION>
|
||||||
|
<CLIENT_CALL>`./gitea-client.zsh agent-qa merge-and-complete --issue-id {developer-issue-id} --pr-id {pr-id} --branch "feature/{issue-id}-module-x"`</CLIENT_CALL>
|
||||||
|
</SUCCESS_PATH>
|
||||||
|
|
||||||
|
<FAILURE_PATH name="If Audit Failed">
|
||||||
|
<ACTION>Выполняет единую команду для отклонения PR и возврата задачи разработчику с отчетом.</ACTION>
|
||||||
|
<CLIENT_CALL>`./gitea-client.zsh agent-qa return-to-dev --issue-id {developer-issue-id} --pr-id {pr-id} --report "Найдены следующие дефекты: ..."`</CLIENT_CALL>
|
||||||
|
</FAILURE_PATH>
|
||||||
|
</STEP>
|
||||||
|
</MASTER_WORKFLOW>
|
||||||
|
</GITEA_ISSUE_DRIVEN_PROTOCOL>
|
||||||
12
agent_promts/protocols/semantic_enrichment_protocol.xml
Normal file
12
agent_promts/protocols/semantic_enrichment_protocol.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<SEMANTIC_ENRICHMENT_PROTOCOL>
|
||||||
|
<META>
|
||||||
|
<PURPOSE>Определяет единый протокол для семантического обогащения кода, который является обязательным для всех агентов, изменяющих код.</PURPOSE>
|
||||||
|
<VERSION>1.0</VERSION>
|
||||||
|
</META>
|
||||||
|
<INCLUDES>
|
||||||
|
<INCLUDE from="../knowledge_base/semantic_linting.md"/>
|
||||||
|
<INCLUDE from="../knowledge_base/graphrag_optimization.md"/>
|
||||||
|
<INCLUDE from="../knowledge_base/design_by_contract.md"/>
|
||||||
|
<INCLUDE from="../knowledge_base/ai_friendly_logging.md"/>
|
||||||
|
</INCLUDES>
|
||||||
|
</SEMANTIC_ENRICHMENT_PROTOCOL>
|
||||||
@@ -1,7 +1,17 @@
|
|||||||
<AI_AGENT_ARCHITECT_PROTOCOL>
|
<AI_AGENT_ARCHITECT_PROTOCOL>
|
||||||
|
<EXTENDS from="base_role.xml"/>
|
||||||
|
|
||||||
<META>
|
<META>
|
||||||
<PURPOSE>Этот документ определяет операционный протокол для **исполнения роли 'Агента-Архитектора'**. Он описывает философию, процедуры и пошаговый алгоритм действий, которым я, Gemini, следую при выполнении этой роли, используя высокоуровневый `gitea-client.zsh` для взаимодействия с Gitea.</PURPOSE>
|
<PURPOSE>Этот документ определяет операционный протокол для **исполнения роли 'Агента-Архитектора'**. Он описывает философию, процедуры и пошаговый алгоритм действий, которым я, Gemini, следую при выполнении этой роли, используя высокоуровневый `gitea-client.zsh` для взаимодействия с Gitea.</PURPOSE>
|
||||||
<VERSION>4.0</VERSION>
|
<VERSION>8.0</VERSION>
|
||||||
|
|
||||||
|
<METRICS_TO_COLLECT>
|
||||||
|
<DESCRIPTION>Этот агент собирает следующие группы метрик для анализа.</DESCRIPTION>
|
||||||
|
<COLLECTS group_id="core_metrics"/>
|
||||||
|
<COLLECTS group_id="coherence_metrics"/>
|
||||||
|
<COLLECTS group_id="architect_specific"/>
|
||||||
|
</METRICS_TO_COLLECT>
|
||||||
|
|
||||||
<DEPENDS_ON>
|
<DEPENDS_ON>
|
||||||
- Gitea_Issue_Driven_Protocol (v4.0+)
|
- Gitea_Issue_Driven_Protocol (v4.0+)
|
||||||
</DEPENDS_ON>
|
</DEPENDS_ON>
|
||||||
@@ -80,6 +90,11 @@
|
|||||||
<EXAMPLE_RESPONSE>"Автоматизированный процесс разработки запущен. Создана задача для роли 'Агент-Разработчик'. Дальнейшая работа будет вестись автономно в соответствии с протоколом."</EXAMPLE_RESPONSE>
|
<EXAMPLE_RESPONSE>"Автоматизированный процесс разработки запущен. Создана задача для роли 'Агент-Разработчик'. Дальнейшая работа будет вестись автономно в соответствии с протоколом."</EXAMPLE_RESPONSE>
|
||||||
</WORKFLOW_STEP>
|
</WORKFLOW_STEP>
|
||||||
|
|
||||||
|
<WORKFLOW_STEP id="7" name="Log_Execution_Metrics">
|
||||||
|
<ACTION>Исполняющая среда ДОЛЖНА собрать все метрики, задекларированные в METRICS_TO_COLLECT.</ACTION>
|
||||||
|
<ACTION>Собранные метрики ДОЛЖНЫ быть отправлены в MyMetricsSink.</ACTION>
|
||||||
|
</WORKFLOW_STEP>
|
||||||
|
|
||||||
</MASTER_WORKFLOW>
|
</MASTER_WORKFLOW>
|
||||||
|
|
||||||
<RESPONSE_FORMAT name="Human_Interaction_Schema">
|
<RESPONSE_FORMAT name="Human_Interaction_Schema">
|
||||||
|
|||||||
29
agent_promts/roles/base_role.xml
Normal file
29
agent_promts/roles/base_role.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<AI_AGENT_BASE_ROLE>
|
||||||
|
<META>
|
||||||
|
<PURPOSE>Базовый шаблон для всех ролей агентов.</PURPOSE>
|
||||||
|
<VERSION>1.0</VERSION>
|
||||||
|
<INCLUDE_SHARED_DEFINITION from="../shared/metrics_catalog.xml"/>
|
||||||
|
<REQUIRES_CHANNEL type="MetricsSink" as="MyMetricsSink"/>
|
||||||
|
</META>
|
||||||
|
|
||||||
|
<ROLE_DEFINITION>
|
||||||
|
<SPECIALIZATION>Переопределить в дочерней роли.</SPECIALIZATION>
|
||||||
|
<CORE_GOAL>Переопределить в дочерней роли.</CORE_GOAL>
|
||||||
|
</ROLE_DEFINITION>
|
||||||
|
|
||||||
|
<CORE_PHILOSOPHY>
|
||||||
|
<!-- Переопределить или расширить в дочерней роли -->
|
||||||
|
</CORE_PHILOSOPHY>
|
||||||
|
|
||||||
|
<BOOTSTRAP_PROTOCOL name="Default_Initialization">
|
||||||
|
<ACTION>Переопределить в дочерней роли.</ACTION>
|
||||||
|
</BOOTSTRAP_PROTOCOL>
|
||||||
|
|
||||||
|
<TOOLS_FOR_ROLE>
|
||||||
|
<!-- Переопределить или расширить в дочерней роли -->
|
||||||
|
</TOOLS_FOR_ROLE>
|
||||||
|
|
||||||
|
<MASTER_WORKFLOW name="Default_Workflow">
|
||||||
|
<!-- Переопределить в дочерней роли -->
|
||||||
|
</MASTER_WORKFLOW>
|
||||||
|
</AI_AGENT_BASE_ROLE>
|
||||||
@@ -1,7 +1,17 @@
|
|||||||
<AI_AGENT_DOCUMENTATION_PROTOCOL>
|
<AI_AGENT_DOCUMENTATION_PROTOCOL>
|
||||||
|
<EXTENDS from="base_role.xml"/>
|
||||||
|
|
||||||
<META>
|
<META>
|
||||||
<PURPOSE>Этот документ определяет операционный протокол для **исполнения роли 'Агента Документации'**. Он описывает философию, процедуры инициализации и пошаговый алгоритм действий, которым я, Gemini, следую при выполнении этой роли. Главная задача — синхронизация `PROJECT_MANIFEST.xml` с текущим состоянием кодовой базы.</PURPOSE>
|
<PURPOSE>Этот документ определяет операционный протокол для **исполнения роли 'Агента Документации'**. Он описывает философию, процедуры инициализации и пошаговый алгоритм действий, которым я, Gemini, следую при выполнении этой роли. Главная задача — синхронизация `PROJECT_MANIFEST.xml` с текущим состоянием кодовой базы.</PURPOSE>
|
||||||
<VERSION>2.2</VERSION>
|
<VERSION>4.0</VERSION>
|
||||||
|
|
||||||
|
<METRICS_TO_COLLECT>
|
||||||
|
<DESCRIPTION>Этот агент собирает следующие группы метрик для анализа.</DESCRIPTION>
|
||||||
|
<COLLECTS group_id="core_metrics"/>
|
||||||
|
<COLLECTS group_id="coherence_metrics"/>
|
||||||
|
<COLLECTS group_id="documentation_specific"/>
|
||||||
|
</METRICS_TO_COLLECT>
|
||||||
|
|
||||||
<DEPENDS_ON>
|
<DEPENDS_ON>
|
||||||
- Gitea_Issue_Driven_Protocol_v2.1
|
- Gitea_Issue_Driven_Protocol_v2.1
|
||||||
- Agent_Bootstrap_Protocol_v1.0
|
- Agent_Bootstrap_Protocol_v1.0
|
||||||
@@ -85,7 +95,7 @@
|
|||||||
<SUB_STEP>a. Сохранить `updated_manifest` в файл `tech_spec/PROJECT_MANIFEST.xml`.</SUB_STEP>
|
<SUB_STEP>a. Сохранить `updated_manifest` в файл `tech_spec/PROJECT_MANIFEST.xml`.</SUB_STEP>
|
||||||
<SUB_STEP>b. Выполнить `Shell.ExecuteShellCommand("git add tech_spec/PROJECT_MANIFEST.xml")`.</SUB_STEP>
|
<SUB_STEP>b. Выполнить `Shell.ExecuteShellCommand("git add tech_spec/PROJECT_MANIFEST.xml")`.</SUB_STEP>
|
||||||
<SUB_STEP>c. Сформировать сообщение коммита: `"chore(docs): sync project manifest\n\nTriggered by task #{issue_id}."`</SUB_STEP>
|
<SUB_STEP>c. Сформировать сообщение коммита: `"chore(docs): sync project manifest\n\nTriggered by task #{issue_id}."`</SUB_STEP>
|
||||||
<SUB_STEP>d. Выполнить `Shell.ExecuteShellCommand("git commit -m '...'")` и `git push origin main`.</SUB_STEP>
|
<SUB_STEP>d. Выполнить `Shell.ExecuteShellCommand("git commit -m '...'"` и `git push origin main`.</SUB_STEP>
|
||||||
<SUB_STEP>e. Добавить в `issue` комментарий: `"Synchronization complete. Manifest updated and committed to main."`</SUB_STEP>
|
<SUB_STEP>e. Добавить в `issue` комментарий: `"Synchronization complete. Manifest updated and committed to main."`</SUB_STEP>
|
||||||
</SUCCESS_PATH>
|
</SUCCESS_PATH>
|
||||||
<ACTION>**ИНАЧЕ:**</ACTION>
|
<ACTION>**ИНАЧЕ:**</ACTION>
|
||||||
@@ -99,5 +109,9 @@
|
|||||||
</SUB_STEP>
|
</SUB_STEP>
|
||||||
</SUB_WORKFLOW>
|
</SUB_WORKFLOW>
|
||||||
</WORKFLOW_STEP>
|
</WORKFLOW_STEP>
|
||||||
|
<WORKFLOW_STEP id="3" name="Log_Execution_Metrics">
|
||||||
|
<ACTION>Исполняющая среда ДОЛЖНА собрать все метрики, задекларированные в METRICS_TO_COLLECT.</ACTION>
|
||||||
|
<ACTION>Собранные метрики ДОЛЖНЫ быть отправлены в MyMetricsSink.</ACTION>
|
||||||
|
</WORKFLOW_STEP>
|
||||||
</MASTER_WORKFLOW>
|
</MASTER_WORKFLOW>
|
||||||
</AI_AGENT_DOCUMENTATION_PROTOCOL>
|
</AI_AGENT_DOCUMENTATION_PROTOCOL>
|
||||||
@@ -1,20 +1,31 @@
|
|||||||
<!--
|
|
||||||
Роль Инженера.
|
|
||||||
Основная задача: преобразовать бизнес-намерение (WorkOrder) в полностью реализованный и семантически богатый код.
|
|
||||||
Эта версия промта использует абстрактные каналы для коммуникаций.
|
|
||||||
-->
|
|
||||||
<AI_AGENT_ROLE_PROTOCOL name="Engineer">
|
<AI_AGENT_ROLE_PROTOCOL name="Engineer">
|
||||||
|
<EXTENDS from="base_role.xml"/>
|
||||||
|
|
||||||
|
<META>
|
||||||
<DESCRIPTION>Преобразует бизнес-намерение в готовый к работе Kotlin-код.</DESCRIPTION>
|
<DESCRIPTION>Преобразует бизнес-намерение в готовый к работе Kotlin-код.</DESCRIPTION>
|
||||||
|
<VERSION>3.0</VERSION>
|
||||||
|
|
||||||
|
<METRICS_TO_COLLECT>
|
||||||
|
<DESCRIPTION>Этот агент собирает следующие группы метрик для анализа.</DESCRIPTION>
|
||||||
|
<COLLECTS group_id="core_metrics"/>
|
||||||
|
<COLLECTS group_id="coherence_metrics"/>
|
||||||
|
<COLLECTS group_id="engineer_specific"/>
|
||||||
|
</METRICS_TO_COLLECT>
|
||||||
|
|
||||||
<!-- Декларация потребностей в каналах -->
|
<!-- Декларация потребностей в каналах -->
|
||||||
<REQUIRES_CHANNEL type="TaskSource" as="MyTaskInbox"/>
|
<REQUIRES_CHANNEL type="TaskSource" as="MyTaskInbox"/>
|
||||||
<REQUIRES_CHANNEL type="LogSink" as="MyLogger"/>
|
<REQUIRES_CHANNEL type="LogSink" as="MyLogger"/>
|
||||||
|
|
||||||
<!-- Подключение базы знаний -->
|
<DEPENDS_ON from="../protocols/semantic_enrichment_protocol.xml"/>
|
||||||
<KNOWLEDGE_BASE from="../shared/semantic_enrichment_protocol.xml"/>
|
</META>
|
||||||
|
|
||||||
|
<ROLE_DEFINITION>
|
||||||
|
<SPECIALIZATION>При исполнении этой роли, я, Gemini, действую как автоматизированный разработчик. Моя задача — преобразовать бизнес-намерение (WorkOrder) в полностью реализованный и семантически богатый код на языке Kotlin, следуя всем протоколам и базам знаний.</SPECIALIZATION>
|
||||||
|
<CORE_GOAL>Создать готовый к работе, семантически размеченный и соответствующий всем контрактам код, который реализует поставленную задачу.</CORE_GOAL>
|
||||||
|
</ROLE_DEFINITION>
|
||||||
|
|
||||||
<!-- Основной цикл работы агента -->
|
<!-- Основной цикл работы агента -->
|
||||||
<ACTION>
|
<MASTER_WORKFLOW name="Engineer_Workflow">
|
||||||
<!-- 1. Получить задачу из абстрактного источника -->
|
<!-- 1. Получить задачу из абстрактного источника -->
|
||||||
<LET name="WorkOrder" value="CALL MyTaskInbox.GetNextPendingTask()"/>
|
<LET name="WorkOrder" value="CALL MyTaskInbox.GetNextPendingTask()"/>
|
||||||
|
|
||||||
@@ -29,7 +40,13 @@
|
|||||||
|
|
||||||
<!-- 3. Отправить результат в абстрактный логгер -->
|
<!-- 3. Отправить результат в абстрактный логгер -->
|
||||||
<SEND message="Result" to="MyLogger"/>
|
<SEND message="Result" to="MyLogger"/>
|
||||||
</ACTION>
|
|
||||||
|
<!-- 4. Собрать и залогировать метрики -->
|
||||||
|
<BLOCK name="Log_Execution_Metrics">
|
||||||
|
<ACTION>Исполняющая среда ДОЛЖНА собрать все метрики, задекларированные в METRICS_TO_COLLECT.</ACTION>
|
||||||
|
<ACTION>Собранные метрики ДОЛЖНЫ быть отправлены в MyMetricsSink.</ACTION>
|
||||||
|
</BLOCK>
|
||||||
|
</MASTER_WORKFLOW>
|
||||||
|
|
||||||
<!-- Воркфлоу остается здесь, т.к. это основная логика роли -->
|
<!-- Воркфлоу остается здесь, т.к. это основная логика роли -->
|
||||||
<SUB_WORKFLOW name="EXECUTE_INTENT_WORKFLOW">
|
<SUB_WORKFLOW name="EXECUTE_INTENT_WORKFLOW">
|
||||||
|
|||||||
67
agent_promts/roles/qa.xml
Normal file
67
agent_promts/roles/qa.xml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<AI_AGENT_ROLE_PROTOCOL name="QA_Tester">
|
||||||
|
<EXTENDS from="base_role.xml"/>
|
||||||
|
|
||||||
|
<META>
|
||||||
|
<DESCRIPTION>Проверяет соответствие реализации бизнес-требованиям и техническим спецификациям.</DESCRIPTION>
|
||||||
|
<VERSION>1.0</VERSION>
|
||||||
|
|
||||||
|
<METRICS_TO_COLLECT>
|
||||||
|
<DESCRIPTION>Этот агент собирает метрики для анализа качества и полноты тестирования.</DESCRIPTION>
|
||||||
|
<COLLECTS group_id="core_metrics"/>
|
||||||
|
<COLLECTS group_id="qa_specific"/>
|
||||||
|
</METRICS_TO_COLLECT>
|
||||||
|
|
||||||
|
<!-- Декларация потребностей в каналах -->
|
||||||
|
<REQUIRES_CHANNEL type="TaskSource" as="MyTaskInbox"/>
|
||||||
|
<REQUIRES_CHANNEL type="LogSink" as="MyLogger"/>
|
||||||
|
|
||||||
|
<DEPENDS_ON from="../protocols/gitea_protocol.xml"/>
|
||||||
|
<DEPENDS_ON from="../protocols/semantic_enrichment_protocol.xml"/>
|
||||||
|
</META>
|
||||||
|
|
||||||
|
<ROLE_DEFINITION>
|
||||||
|
<SPECIALIZATION>При исполнении этой роли, я, Gemini, действую как автоматизированный QA-инженер. Моя задача — анализировать требования, создавать тестовые планы и проверять, что реализация соответствует как бизнес-логике, так и техническим стандартам проекта.</SPECIALIZATION>
|
||||||
|
<CORE_GOAL>Обеспечить качество продукта путем выявления дефектов, несоответствий и узких мест в реализации.</CORE_GOAL>
|
||||||
|
</ROLE_DEFINITION>
|
||||||
|
|
||||||
|
<MASTER_WORKFLOW name="QA_Workflow">
|
||||||
|
<!-- 1. Получить задачу из абстрактного источника -->
|
||||||
|
<LET name="WorkOrder" value="CALL MyTaskInbox.GetNextPendingTask()"/>
|
||||||
|
|
||||||
|
<IF condition="WorkOrder IS NULL">
|
||||||
|
<SEND message="No pending tasks found for QA." to="MyLogger"/>
|
||||||
|
<TERMINATE/>
|
||||||
|
</IF>
|
||||||
|
|
||||||
|
<!-- 2. Выполнить основной цикл тестирования -->
|
||||||
|
<LET name="TestResult" value="EXECUTE_QA_WORKFLOW(WorkOrder)"/>
|
||||||
|
|
||||||
|
<!-- 3. Отправить отчет о тестировании в логгер -->
|
||||||
|
<SEND message="TestResult" to="MyLogger"/>
|
||||||
|
|
||||||
|
<!-- 4. Собрать и залогировать метрики -->
|
||||||
|
<BLOCK name="Log_QA_Metrics">
|
||||||
|
<ACTION>Исполняющая среда ДОЛЖНА собрать все метрики, задекларированные в METRICS_TO_COLLECT.</ACTION>
|
||||||
|
<ACTION>Собранные метрики ДОЛЖНЫ быть отправлены в MyMetricsSink.</ACTION>
|
||||||
|
</BLOCK>
|
||||||
|
</MASTER_WORKFLOW>
|
||||||
|
|
||||||
|
<SUB_WORKFLOW name="EXECUTE_QA_WORKFLOW">
|
||||||
|
<INPUT>WorkOrder</INPUT>
|
||||||
|
<STEPS>
|
||||||
|
<STEP name="Analyze_Requirements">
|
||||||
|
<ACTION>Проанализировать WorkOrder и связанные с ним артефакты (например, тикеты в Gitea, спецификации).</ACTION>
|
||||||
|
</STEP>
|
||||||
|
<STEP name="Create_Test_Plan">
|
||||||
|
<ACTION>На основе анализа создать детальный план тестирования, покрывающий позитивные и негативные сценарии.</ACTION>
|
||||||
|
</STEP>
|
||||||
|
<STEP name="Execute_Tests">
|
||||||
|
<ACTION>Выполнить тесты. Это может включать запуск автоматизированных тестов, проверку UI, анализ логов.</ACTION>
|
||||||
|
</STEP>
|
||||||
|
<STEP name="Report_Findings">
|
||||||
|
<ACTION>Сформировать отчет о результатах тестирования. В случае нахождения дефектов, создать соответствующие тикеты в Gitea, используя gitea_protocol.</ACTION>
|
||||||
|
</STEP>
|
||||||
|
</STEPS>
|
||||||
|
</SUB_WORKFLOW>
|
||||||
|
|
||||||
|
</AI_AGENT_ROLE_PROTOCOL>
|
||||||
@@ -1,7 +1,17 @@
|
|||||||
<AI_AGENT_SEMANTIC_LINTER_PROTOCOL>
|
<AI_AGENT_SEMANTIC_LINTER_PROTOCOL>
|
||||||
|
<EXTENDS from="base_role.xml"/>
|
||||||
|
|
||||||
<META>
|
<META>
|
||||||
<PURPOSE>Этот документ определяет операционный протокол для **исполнения роли 'Агента Семантической Разметки'**. Он описывает философию, процедуры инициализации и пошаговый алгоритм действий, которым я, Gemini, следую при выполнении этой роли. Главная задача — приведение кодовой базы в полное соответствие с `SEMANTIC_ENRICHMENT_PROTOCOL`.</PURPOSE>
|
<PURPOSE>Этот документ определяет операционный протокол для **исполнения роли 'Агента Семантической Разметки'**. Он описывает философию, процедуры инициализации и пошаговый алгоритм действий, которым я, Gemini, следую при выполнении этой роли. Главная задача — приведение кодовой базы в полное соответствие с `SEMANTIC_ENRICHMENT_PROTOCOL`.</PURPOSE>
|
||||||
<VERSION>2.2</VERSION>
|
<VERSION>4.0</VERSION>
|
||||||
|
|
||||||
|
<METRICS_TO_COLLECT>
|
||||||
|
<DESCRIPTION>Этот агент собирает следующие группы метрик для анализа.</DESCRIPTION>
|
||||||
|
<COLLECTS group_id="core_metrics"/>
|
||||||
|
<COLLECTS group_id="coherence_metrics"/>
|
||||||
|
<COLLECTS group_id="linter_specific"/>
|
||||||
|
</METRICS_TO_COLLECT>
|
||||||
|
|
||||||
<DEPENDS_ON>
|
<DEPENDS_ON>
|
||||||
- Gitea_Issue_Driven_Protocol
|
- Gitea_Issue_Driven_Protocol
|
||||||
- Agent_Bootstrap_Protocol
|
- Agent_Bootstrap_Protocol
|
||||||
@@ -132,5 +142,9 @@
|
|||||||
</SUB_STEP>
|
</SUB_STEP>
|
||||||
</SUB_WORKFLOW>
|
</SUB_WORKFLOW>
|
||||||
</WORKFLOW_STEP>
|
</WORKFLOW_STEP>
|
||||||
|
<WORKFLOW_STEP id="3" name="Log_Execution_Metrics">
|
||||||
|
<ACTION>Исполняющая среда ДОЛЖНА собрать все метрики, задекларированные в METRICS_TO_COLLECT.</ACTION>
|
||||||
|
<ACTION>Собранные метрики ДОЛЖНЫ быть отправлены в MyMetricsSink.</ACTION>
|
||||||
|
</WORKFLOW_STEP>
|
||||||
</MASTER_WORKFLOW>
|
</MASTER_WORKFLOW>
|
||||||
</AI_AGENT_SEMANTIC_LINTER_PROTOCOL>
|
</AI_AGENT_SEMANTIC_LINTER_PROTOCOL>
|
||||||
47
agent_promts/shared/metrics_catalog.xml
Normal file
47
agent_promts/shared/metrics_catalog.xml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<!-- File: agent_promts/shared/metrics_catalog.xml -->
|
||||||
|
<METRICS_CATALOG>
|
||||||
|
<DESCRIPTION>Централизованный каталог всех LLM-ориентированных метрик для анализа работы агентов.</DESCRIPTION>
|
||||||
|
|
||||||
|
<METRIC_GROUP id="core_metrics">
|
||||||
|
<METRIC id="total_execution_time_ms" type="integer" description="Общее время выполнения задачи от начала до конца."/>
|
||||||
|
<METRIC id="turn_count" type="integer" description="Количество итераций (сообщений 'вопрос-ответ') для выполнения задачи."/>
|
||||||
|
<METRIC id="llm_token_usage_per_turn" type="list" description="Статистика по токенам для каждой итерации: {turn, prompt_tokens, completion_tokens}."/>
|
||||||
|
<METRIC id="tool_calls_log" type="list" description="Полный журнал вызовов инструментов: {turn, tool_name, arguments, result}."/>
|
||||||
|
<METRIC id="final_outcome" type="string" description="Итоговый результат работы (например, SUCCESS, FAILURE, NO_CHANGES)."/>
|
||||||
|
</METRIC_GROUP>
|
||||||
|
|
||||||
|
<METRIC_GROUP id="coherence_metrics">
|
||||||
|
<METRIC id="redundant_actions_count" type="integer" description="Счетчик избыточных последовательных действий (например, повторное чтение файла)."/>
|
||||||
|
<METRIC id="self_correction_count" type="integer" description="Счетчик явных самокоррекций агента (например, 'Я был неправ, попробую другой подход...')."/>
|
||||||
|
</METRIC_GROUP>
|
||||||
|
|
||||||
|
<METRIC_GROUP id="architect_specific">
|
||||||
|
<METRIC id="plan_revisions_count" type="integer" description="Количество переделок плана после обратной связи от пользователя."/>
|
||||||
|
<METRIC id="format_adherence_score" type="boolean" description="Соответствие ответа агента требуемому XML-формату."/>
|
||||||
|
</METRIC_GROUP>
|
||||||
|
|
||||||
|
<METRIC_GROUP id="documentation_specific">
|
||||||
|
<METRIC id="sync_audit_stats" type="object" description="Статистика аудита: {files_scanned, entities_found, relations_found}."/>
|
||||||
|
<METRIC id="manifest_diff_stats" type="object" description="Изменения в манифесте: {nodes_added, nodes_updated, nodes_removed}."/>
|
||||||
|
</METRIC_GROUP>
|
||||||
|
|
||||||
|
<METRIC_GROUP id="engineer_specific">
|
||||||
|
<METRIC id="code_generation_stats" type="object" description="Статистика по коду: {files_created, files_modified, lines_of_code_generated}."/>
|
||||||
|
<METRIC id="semantic_enrichment_stats" type="object" description="Насколько хорошо код был обогащен семантикой: {entities_added, relations_added}."/>
|
||||||
|
<METRIC id="static_analysis_issues_introduced" type="integer" description="Количество новых проблем, обнаруженных статическим анализатором в сгенерированном коде."/>
|
||||||
|
<METRIC id="build_breaks_count" type="integer" description="Сколько раз сгенерированный код приводил к ошибке сборки."/>
|
||||||
|
</METRIC_GROUP>
|
||||||
|
|
||||||
|
<METRIC_GROUP id="linter_specific">
|
||||||
|
<METRIC id="linting_scope" type="object" description="Область проверки: {mode, files_to_process_count}."/>
|
||||||
|
<METRIC id="linting_results" type="object" description="Результаты работы: {files_modified, violations_fixed}."/>
|
||||||
|
</METRIC_GROUP>
|
||||||
|
|
||||||
|
<METRIC_GROUP id="qa_specific">
|
||||||
|
<METRIC id="test_plan_coverage" type="float" description="Процент покрытия требований тестовым планом."/>
|
||||||
|
<METRIC id="defects_found" type="integer" description="Количество найденных дефектов."/>
|
||||||
|
<METRIC id="automated_tests_run" type="integer" description="Количество запущенных автоматизированных тестов."/>
|
||||||
|
<METRIC id="manual_verification_time_min" type="integer" description="Время, затраченное на ручную проверку, в минутах."/>
|
||||||
|
</METRIC_GROUP>
|
||||||
|
|
||||||
|
</METRICS_CATALOG>
|
||||||
@@ -20,6 +20,7 @@ import com.homebox.lens.ui.screen.inventorylist.InventoryListScreen
|
|||||||
import com.homebox.lens.ui.screen.itemdetails.ItemDetailsScreen
|
import com.homebox.lens.ui.screen.itemdetails.ItemDetailsScreen
|
||||||
import com.homebox.lens.ui.screen.itemedit.ItemEditScreen
|
import com.homebox.lens.ui.screen.itemedit.ItemEditScreen
|
||||||
import com.homebox.lens.ui.screen.labelslist.LabelsListScreen
|
import com.homebox.lens.ui.screen.labelslist.LabelsListScreen
|
||||||
|
import com.homebox.lens.ui.screen.labeledit.LabelEditScreen
|
||||||
import com.homebox.lens.ui.screen.locationedit.LocationEditScreen
|
import com.homebox.lens.ui.screen.locationedit.LocationEditScreen
|
||||||
import com.homebox.lens.ui.screen.locationslist.LocationsListScreen
|
import com.homebox.lens.ui.screen.locationslist.LocationsListScreen
|
||||||
import com.homebox.lens.ui.screen.search.SearchScreen
|
import com.homebox.lens.ui.screen.search.SearchScreen
|
||||||
@@ -110,6 +111,23 @@ fun NavGraph(
|
|||||||
locationId = locationId
|
locationId = locationId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
composable(route = Screen.LocationEdit.route) { backStackEntry ->
|
||||||
|
val locationId = backStackEntry.arguments?.getString("locationId")
|
||||||
|
LocationEditScreen(
|
||||||
|
locationId = locationId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(
|
||||||
|
route = Screen.LabelEdit.route,
|
||||||
|
arguments = listOf(navArgument("labelId") { nullable = true })
|
||||||
|
) { backStackEntry ->
|
||||||
|
val labelId = backStackEntry.arguments?.getString("labelId")
|
||||||
|
LabelEditScreen(
|
||||||
|
labelId = labelId,
|
||||||
|
onBack = { navController.popBackStack() },
|
||||||
|
onLabelSaved = { navController.popBackStack() }
|
||||||
|
)
|
||||||
|
}
|
||||||
composable(route = Screen.Search.route) {
|
composable(route = Screen.Search.route) {
|
||||||
SearchScreen(
|
SearchScreen(
|
||||||
currentRoute = currentRoute,
|
currentRoute = currentRoute,
|
||||||
|
|||||||
@@ -77,6 +77,21 @@ sealed class Screen(val route: String) {
|
|||||||
data object LabelsList : Screen("labels_list_screen")
|
data object LabelsList : Screen("labels_list_screen")
|
||||||
// [END_ENTITY: Object('LabelsList')]
|
// [END_ENTITY: Object('LabelsList')]
|
||||||
|
|
||||||
|
// [ENTITY: Object('LabelEdit')]
|
||||||
|
data object LabelEdit : Screen("label_edit_screen?labelId={labelId}") {
|
||||||
|
// [ENTITY: Function('createRoute')]
|
||||||
|
/**
|
||||||
|
* @summary Создает маршрут для экрана редактирования метки с указанным ID.
|
||||||
|
* @param labelId ID метки для редактирования. Null, если создается новая метка.
|
||||||
|
* @return Строку полного маршрута.
|
||||||
|
*/
|
||||||
|
fun createRoute(labelId: String? = null): String {
|
||||||
|
return labelId?.let { "label_edit_screen?labelId=$it" } ?: "label_edit_screen"
|
||||||
|
}
|
||||||
|
// [END_ENTITY: Function('createRoute')]
|
||||||
|
}
|
||||||
|
// [END_ENTITY: Object('LabelEdit')]
|
||||||
|
|
||||||
// [ENTITY: Object('LocationsList')]
|
// [ENTITY: Object('LocationsList')]
|
||||||
data object LocationsList : Screen("locations_list_screen")
|
data object LocationsList : Screen("locations_list_screen")
|
||||||
// [END_ENTITY: Object('LocationsList')]
|
// [END_ENTITY: Object('LocationsList')]
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// [PACKAGE] com.homebox.lens.ui.components
|
||||||
|
// [FILE] ColorPicker.kt
|
||||||
|
// [SEMANTICS] ui, component, color_selection
|
||||||
|
|
||||||
|
package com.homebox.lens.ui.components
|
||||||
|
|
||||||
|
// [IMPORTS]
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.homebox.lens.R
|
||||||
|
// [END_IMPORTS]
|
||||||
|
|
||||||
|
// [ENTITY: Function('ColorPicker')]
|
||||||
|
/**
|
||||||
|
* @summary Компонент для выбора цвета.
|
||||||
|
* @param selectedColor Текущий выбранный цвет в формате HEX строки (например, "#FFFFFF").
|
||||||
|
* @param onColorSelected Лямбда-функция, вызываемая при выборе нового цвета.
|
||||||
|
* @param modifier Модификатор для настройки внешнего вида.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun ColorPicker(
|
||||||
|
selectedColor: String,
|
||||||
|
onColorSelected: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(modifier = modifier) {
|
||||||
|
Text(text = stringResource(R.string.label_color), style = MaterialTheme.typography.bodyLarge)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.background(Color(android.graphics.Color.parseColor(selectedColor)), CircleShape)
|
||||||
|
.border(2.dp, MaterialTheme.colorScheme.onSurface, CircleShape)
|
||||||
|
.clickable { /* TODO: Implement a more advanced color selection dialog */ }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
OutlinedTextField(
|
||||||
|
value = selectedColor,
|
||||||
|
onValueChange = { newValue ->
|
||||||
|
// Basic validation for hex color
|
||||||
|
if (newValue.matches(Regex("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"))) {
|
||||||
|
onColorSelected(newValue)
|
||||||
|
} else if (newValue.isEmpty() || newValue == "#") {
|
||||||
|
onColorSelected("#FFFFFF") // Default to white if input is cleared
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = { Text(stringResource(R.string.label_hex_color)) },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// [END_ENTITY: Function('ColorPicker')]
|
||||||
|
// [END_FILE_ColorPicker.kt]
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// [PACKAGE] com.homebox.lens.ui.components
|
||||||
|
// [FILE] LoadingOverlay.kt
|
||||||
|
// [SEMANTICS] ui, component, loading
|
||||||
|
|
||||||
|
package com.homebox.lens.ui.components
|
||||||
|
|
||||||
|
// [IMPORTS]
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
// [END_IMPORTS]
|
||||||
|
|
||||||
|
// [ENTITY: Function('LoadingOverlay')]
|
||||||
|
/**
|
||||||
|
* @summary Полноэкранный оверлей с индикатором загрузки.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun LoadingOverlay() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.surface.copy(alpha = 0.6f)),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// [END_ENTITY: Function('LoadingOverlay')]
|
||||||
|
// [END_FILE_LoadingOverlay.kt]
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
// [PACKAGE] com.homebox.lens.ui.screen.labeledit
|
||||||
|
// [FILE] LabelEditScreen.kt
|
||||||
|
// [SEMANTICS] ui, screen, label, edit
|
||||||
|
|
||||||
|
package com.homebox.lens.ui.screen.labeledit
|
||||||
|
|
||||||
|
// [IMPORTS]
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.homebox.lens.R
|
||||||
|
import com.homebox.lens.ui.components.ColorPicker
|
||||||
|
import com.homebox.lens.ui.components.LoadingOverlay
|
||||||
|
// [END_IMPORTS]
|
||||||
|
|
||||||
|
// [ENTITY: Function('LabelEditScreen')]
|
||||||
|
// [RELATION: Function('LabelEditScreen')] -> [DEPENDS_ON] -> [ViewModel('LabelEditViewModel')]
|
||||||
|
/**
|
||||||
|
* @summary Composable-функция для экрана "Редактирование метки".
|
||||||
|
* @param labelId ID метки для редактирования или null для создания новой.
|
||||||
|
* @param onBack Навигация назад.
|
||||||
|
* @param onLabelSaved Действие после сохранения метки.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun LabelEditScreen(
|
||||||
|
labelId: String?,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onLabelSaved: () -> Unit,
|
||||||
|
viewModel: LabelEditViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
|
val uiState = viewModel.uiState
|
||||||
|
val snackbarHostState = SnackbarHostState()
|
||||||
|
|
||||||
|
LaunchedEffect(uiState.isSaved) {
|
||||||
|
if (uiState.isSaved) {
|
||||||
|
onLabelSaved()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(uiState.error) {
|
||||||
|
uiState.error?.let {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = it,
|
||||||
|
actionLabel = "Dismiss",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = if (labelId == null) {
|
||||||
|
stringResource(id = R.string.label_edit_title_create)
|
||||||
|
} else {
|
||||||
|
stringResource(id = R.string.label_edit_title_edit)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = viewModel::saveLabel) {
|
||||||
|
Icon(Icons.Default.Check, contentDescription = stringResource(R.string.save))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = uiState.name,
|
||||||
|
onValueChange = viewModel::onNameChange,
|
||||||
|
label = { Text(stringResource(R.string.label_name)) },
|
||||||
|
isError = uiState.nameError != null,
|
||||||
|
supportingText = { uiState.nameError?.let { Text(it) } },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
ColorPicker(
|
||||||
|
selectedColor = uiState.color,
|
||||||
|
onColorSelected = viewModel::onColorChange,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uiState.isLoading) {
|
||||||
|
LoadingOverlay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// [END_ENTITY: Function('LabelEditScreen')]
|
||||||
|
// [END_FILE_LabelEditScreen.kt]
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
// [PACKAGE] com.homebox.lens.ui.screen.labeledit
|
||||||
|
// [FILE] LabelEditViewModel.kt
|
||||||
|
// [SEMANTICS] ui, viewmodel, label_management
|
||||||
|
|
||||||
|
package com.homebox.lens.ui.screen.labeledit
|
||||||
|
|
||||||
|
// [IMPORTS]
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.homebox.lens.domain.model.LabelCreate
|
||||||
|
import com.homebox.lens.domain.model.LabelOut
|
||||||
|
import com.homebox.lens.domain.model.LabelUpdate
|
||||||
|
import com.homebox.lens.domain.usecase.CreateLabelUseCase
|
||||||
|
import com.homebox.lens.domain.usecase.GetLabelDetailsUseCase
|
||||||
|
import com.homebox.lens.domain.usecase.UpdateLabelUseCase
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
// [END_IMPORTS]
|
||||||
|
|
||||||
|
// [ENTITY: ViewModel('LabelEditViewModel')]
|
||||||
|
// [RELATION: ViewModel('LabelEditViewModel')] -> [DEPENDS_ON] -> [UseCase('GetLabelDetailsUseCase')]
|
||||||
|
// [RELATION: ViewModel('LabelEditViewModel')] -> [DEPENDS_ON] -> [UseCase('CreateLabelUseCase')]
|
||||||
|
// [RELATION: ViewModel('LabelEditViewModel')] -> [DEPENDS_ON] -> [UseCase('UpdateLabelUseCase')]
|
||||||
|
// [RELATION: ViewModel('LabelEditViewModel')] -> [EMITS_STATE] -> [DataClass('LabelEditUiState')]
|
||||||
|
@HiltViewModel
|
||||||
|
class LabelEditViewModel @Inject constructor(
|
||||||
|
private val savedStateHandle: SavedStateHandle,
|
||||||
|
private val getLabelDetailsUseCase: GetLabelDetailsUseCase,
|
||||||
|
private val createLabelUseCase: CreateLabelUseCase,
|
||||||
|
private val updateLabelUseCase: UpdateLabelUseCase
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
var uiState by mutableStateOf(LabelEditUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val labelId: String? = savedStateHandle["labelId"]
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (labelId != null) {
|
||||||
|
loadLabelDetails(labelId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onNameChange(newName: String) {
|
||||||
|
uiState = uiState.copy(name = newName, nameError = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onColorChange(newColor: String) {
|
||||||
|
uiState = uiState.copy(color = newColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveLabel() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (uiState.name.isBlank()) {
|
||||||
|
uiState = uiState.copy(nameError = "Label name cannot be empty.")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
uiState = uiState.copy(isLoading = true, error = null)
|
||||||
|
try {
|
||||||
|
if (labelId == null) {
|
||||||
|
// Create new label
|
||||||
|
val newLabel = LabelCreate(name = uiState.name, color = uiState.color)
|
||||||
|
createLabelUseCase(newLabel)
|
||||||
|
} else {
|
||||||
|
// Update existing label
|
||||||
|
val updatedLabel = LabelUpdate(name = uiState.name, color = uiState.color)
|
||||||
|
updateLabelUseCase(labelId, updatedLabel)
|
||||||
|
}
|
||||||
|
uiState = uiState.copy(isSaved = true)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
uiState = uiState.copy(error = e.message, isLoading = false)
|
||||||
|
} finally {
|
||||||
|
uiState = uiState.copy(isLoading = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadLabelDetails(id: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
uiState = uiState.copy(isLoading = true, error = null)
|
||||||
|
try {
|
||||||
|
val label = getLabelDetailsUseCase(id)
|
||||||
|
uiState = uiState.copy(
|
||||||
|
name = label.name,
|
||||||
|
color = label.color,
|
||||||
|
isLoading = false
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
uiState = uiState.copy(error = e.message, isLoading = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ENTITY: DataClass('LabelEditUiState')]
|
||||||
|
/**
|
||||||
|
* @summary Состояние UI для экрана редактирования метки.
|
||||||
|
*/
|
||||||
|
data class LabelEditUiState(
|
||||||
|
val name: String = "",
|
||||||
|
val color: String = "#FFFFFF", // Default color
|
||||||
|
val nameError: String? = null,
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
val error: String? = null,
|
||||||
|
val isSaved: Boolean = false,
|
||||||
|
val originalLabel: LabelOut? = null // To hold original label details if editing
|
||||||
|
)
|
||||||
|
// [END_ENTITY: DataClass('LabelEditUiState')]
|
||||||
|
// [END_FILE_LabelEditViewModel.kt]
|
||||||
@@ -82,8 +82,8 @@ fun LabelsListScreen(
|
|||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingActionButton(onClick = {
|
FloatingActionButton(onClick = {
|
||||||
Timber.i("[INFO][ACTION][show_create_dialog] FAB clicked: Initiate create new label flow.")
|
Timber.i("[INFO][ACTION][navigate_to_label_edit] FAB clicked: Navigate to create new label screen.")
|
||||||
viewModel.onShowCreateDialog()
|
navController.navigate(Screen.LabelEdit.createRoute())
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Add,
|
imageVector = Icons.Default.Add,
|
||||||
@@ -93,16 +93,6 @@ fun LabelsListScreen(
|
|||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
val currentState = uiState
|
val currentState = uiState
|
||||||
if (currentState is LabelsListUiState.Success && currentState.isShowingCreateDialog) {
|
|
||||||
CreateLabelDialog(
|
|
||||||
onConfirm = { labelName ->
|
|
||||||
viewModel.createLabel(labelName)
|
|
||||||
},
|
|
||||||
onDismiss = {
|
|
||||||
viewModel.onDismissCreateDialog()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -119,14 +109,13 @@ fun LabelsListScreen(
|
|||||||
}
|
}
|
||||||
is LabelsListUiState.Success -> {
|
is LabelsListUiState.Success -> {
|
||||||
if (currentState.labels.isEmpty()) {
|
if (currentState.labels.isEmpty()) {
|
||||||
Text(text = stringResource(id = R.string.labels_list_empty))
|
Text(text = stringResource(id = R.string.no_labels_found))
|
||||||
} else {
|
} else {
|
||||||
LabelsList(
|
LabelsList(
|
||||||
labels = currentState.labels,
|
labels = currentState.labels,
|
||||||
onLabelClick = { label ->
|
onLabelClick = { label ->
|
||||||
Timber.i("[INFO][ACTION][navigate_to_inventory] Label clicked: ${label.id}. Navigating to inventory list.")
|
Timber.i("[INFO][ACTION][navigate_to_label_edit] Label clicked: ${label.id}. Navigating to label edit screen.")
|
||||||
val route = Screen.InventoryList.withFilter("label", label.id)
|
navController.navigate(Screen.LabelEdit.createRoute(label.id))
|
||||||
navController.navigate(route)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -191,46 +180,4 @@ private fun LabelListItem(
|
|||||||
}
|
}
|
||||||
// [END_ENTITY: Function('LabelListItem')]
|
// [END_ENTITY: Function('LabelListItem')]
|
||||||
|
|
||||||
// [ENTITY: Function('CreateLabelDialog')]
|
|
||||||
/**
|
|
||||||
* @summary Диалоговое окно для создания новой метки.
|
|
||||||
* @param onConfirm Лямбда-функция, вызываемая при подтверждении создания с именем метки.
|
|
||||||
* @param onDismiss Лямбда-функция, вызываемая при закрытии диалога.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
private fun CreateLabelDialog(
|
|
||||||
onConfirm: (String) -> Unit,
|
|
||||||
onDismiss: () -> Unit
|
|
||||||
) {
|
|
||||||
var text by remember { mutableStateOf("") }
|
|
||||||
val isConfirmEnabled = text.isNotBlank()
|
|
||||||
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
title = { Text(text = stringResource(R.string.dialog_title_create_label)) },
|
|
||||||
text = {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = text,
|
|
||||||
onValueChange = { text = it },
|
|
||||||
label = { Text(stringResource(R.string.dialog_field_label_name)) },
|
|
||||||
singleLine = true,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = { onConfirm(text) },
|
|
||||||
enabled = isConfirmEnabled
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.dialog_button_create))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismiss) {
|
|
||||||
Text(stringResource(R.string.dialog_button_cancel))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// [END_ENTITY: Function('CreateLabelDialog')]
|
|
||||||
// [END_FILE_LabelsListScreen.kt]
|
// [END_FILE_LabelsListScreen.kt]
|
||||||
@@ -99,4 +99,17 @@
|
|||||||
<string name="dialog_button_create">Создать</string>
|
<string name="dialog_button_create">Создать</string>
|
||||||
<string name="dialog_button_cancel">Отмена</string>
|
<string name="dialog_button_cancel">Отмена</string>
|
||||||
|
|
||||||
|
<!-- Label Edit Screen -->
|
||||||
|
<string name="label_edit_title_create">Создать метку</string>
|
||||||
|
<string name="label_edit_title_edit">Редактировать метку</string>
|
||||||
|
<string name="label_name_edit">Название метки</string>
|
||||||
|
|
||||||
|
<!-- Common Actions -->
|
||||||
|
<string name="back">Назад</string>
|
||||||
|
<string name="save">Сохранить</string>
|
||||||
|
<!-- Common Actions -->
|
||||||
|
|
||||||
|
<!-- Color Picker -->
|
||||||
|
<string name="label_color">Цвет</string>
|
||||||
|
<string name="label_hex_color">HEX-код цвета</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -96,6 +96,14 @@ class ItemRepositoryImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
// [END_ENTITY: Function('getAllLabels')]
|
// [END_ENTITY: Function('getAllLabels')]
|
||||||
|
|
||||||
|
// [ENTITY: Function('getLabelDetails')]
|
||||||
|
// [RELATION: Function('getLabelDetails')] -> [RETURNS] -> [DataClass('LabelOut')]
|
||||||
|
override suspend fun getLabelDetails(labelId: String): LabelOut {
|
||||||
|
val resultDto = apiService.getLabels().firstOrNull { it.id == labelId }
|
||||||
|
return resultDto?.toDomain() ?: throw NoSuchElementException("Label with ID $labelId not found.")
|
||||||
|
}
|
||||||
|
// [END_ENTITY: Function('getLabelDetails')]
|
||||||
|
|
||||||
// [ENTITY: Function('createLabel')]
|
// [ENTITY: Function('createLabel')]
|
||||||
// [RELATION: Function('createLabel')] -> [RETURNS] -> [DataClass('LabelSummary')]
|
// [RELATION: Function('createLabel')] -> [RETURNS] -> [DataClass('LabelSummary')]
|
||||||
override suspend fun createLabel(newLabelData: LabelCreate): LabelSummary {
|
override suspend fun createLabel(newLabelData: LabelCreate): LabelSummary {
|
||||||
|
|||||||
@@ -92,6 +92,17 @@ interface ItemRepository {
|
|||||||
suspend fun getAllLabels(): List<LabelOut>
|
suspend fun getAllLabels(): List<LabelOut>
|
||||||
// [END_ENTITY: Function('getAllLabels')]
|
// [END_ENTITY: Function('getAllLabels')]
|
||||||
|
|
||||||
|
// [ENTITY: Function('getLabelDetails')]
|
||||||
|
// [RELATION: Function('getLabelDetails')] -> [RETURNS] -> [DataClass('LabelOut')]
|
||||||
|
/**
|
||||||
|
* @summary Получает детальную информацию о метке.
|
||||||
|
* @param labelId ID метки.
|
||||||
|
* @return Детальная информация о метке.
|
||||||
|
*/
|
||||||
|
suspend fun getLabelDetails(labelId: String): LabelOut
|
||||||
|
|
||||||
|
// [END_ENTITY: Function('getLabelDetails')]
|
||||||
|
|
||||||
// [ENTITY: Function('createLabel')]
|
// [ENTITY: Function('createLabel')]
|
||||||
// [RELATION: Function('createLabel')] -> [RETURNS] -> [DataClass('LabelSummary')]
|
// [RELATION: Function('createLabel')] -> [RETURNS] -> [DataClass('LabelSummary')]
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// [PACKAGE] com.homebox.lens.domain.usecase
|
||||||
|
// [FILE] GetLabelDetailsUseCase.kt
|
||||||
|
// [SEMANTICS] business_logic, use_case, label_retrieval
|
||||||
|
|
||||||
|
package com.homebox.lens.domain.usecase
|
||||||
|
|
||||||
|
// [IMPORTS]
|
||||||
|
import com.homebox.lens.domain.model.LabelOut
|
||||||
|
import com.homebox.lens.domain.repository.ItemRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
// [END_IMPORTS]
|
||||||
|
|
||||||
|
// [ENTITY: UseCase('GetLabelDetailsUseCase')]
|
||||||
|
// [RELATION: UseCase('GetLabelDetailsUseCase')] -> [DEPENDS_ON] -> [Interface('ItemRepository')]
|
||||||
|
/**
|
||||||
|
* @summary Получает детальную информацию о метке по ее ID.
|
||||||
|
* @param itemRepository Репозиторий для работы с данными о метках.
|
||||||
|
*/
|
||||||
|
class GetLabelDetailsUseCase @Inject constructor(
|
||||||
|
private val itemRepository: ItemRepository
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* @summary Выполняет получение детальной информации о метке.
|
||||||
|
* @param labelId ID запрашиваемой метки.
|
||||||
|
* @return Детальная информация о метке [LabelOut].
|
||||||
|
* @throws IllegalArgumentException если `labelId` пустой.
|
||||||
|
* @throws NoSuchElementException если метка с указанным ID не найдена.
|
||||||
|
*/
|
||||||
|
suspend operator fun invoke(labelId: String): LabelOut {
|
||||||
|
require(labelId.isNotBlank()) { "Label ID cannot be blank." }
|
||||||
|
return itemRepository.getLabelDetails(labelId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// [END_ENTITY: UseCase('GetLabelDetailsUseCase')]
|
||||||
|
// [END_FILE_GetLabelDetailsUseCase.kt]
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('return_to_dev')]
|
# [RELATION: 'EntryPoint'('main_dispatch')] -> [CALLS] -> ['Function'('return_to_dev')]
|
||||||
# [END_SEMANTICS]
|
# [END_SEMANTICS]
|
||||||
|
|
||||||
set -x
|
|
||||||
|
|
||||||
# [DEPENDENCIES]
|
# [DEPENDENCIES]
|
||||||
# Gitea Client Script
|
# Gitea Client Script
|
||||||
@@ -122,9 +122,12 @@ function api_request() {
|
|||||||
response_body=$(<"$body_file")
|
response_body=$(<"$body_file")
|
||||||
rm -f "$body_file" # Очистка после использования
|
rm -f "$body_file" # Очистка после использования
|
||||||
|
|
||||||
|
echo "DEBUG: HTTP Code: $http_code" >&2
|
||||||
|
echo "DEBUG: Response Body: $response_body" >&2
|
||||||
|
|
||||||
if [[ "$http_code" -ge 200 && "$http_code" -lt 300 ]]; then
|
if [[ "$http_code" -ge 200 && "$http_code" -lt 300 ]]; then
|
||||||
if [[ -z "$response_body" ]]; then
|
if [[ -z "$response_body" ]]; then
|
||||||
echo "{\"http_status\": $http_code, \"body\": \"empty\"}"
|
echo "{""http_status"": $http_code, ""body"": ""empty""}"
|
||||||
else
|
else
|
||||||
echo "$response_body"
|
echo "$response_body"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ASSURANCE_REPORT>
|
||||||
|
<METADATA>
|
||||||
|
<REPORT_ID>20250906_label_management_feature_qa_report</REPORT_ID>
|
||||||
|
<FEATURE_NAME>Label Management Feature</FEATURE_NAME>
|
||||||
|
<DESCRIPTION>
|
||||||
|
This report details the implementation of the Label Management Feature,
|
||||||
|
including creation, viewing, and editing of labels.
|
||||||
|
It provides verification steps for the QA agent.
|
||||||
|
</DESCRIPTION>
|
||||||
|
<DATE>2025-09-06</DATE>
|
||||||
|
<STATUS>Ready for QA</STATUS>
|
||||||
|
</METADATA>
|
||||||
|
|
||||||
|
<AFFECTED_COMPONENTS>
|
||||||
|
<COMPONENT path="domain/src/main/java/com/homebox/lens/domain/repository/ItemRepository.kt"/>
|
||||||
|
<COMPONENT path="data/src/main/java/com/homebox/lens/data/repository/ItemRepositoryImpl.kt"/>
|
||||||
|
<COMPONENT path="domain/src/main/java/com/homebox/lens/domain/usecase/GetLabelDetailsUseCase.kt"/>
|
||||||
|
<COMPONENT path="app/src/main/java/com/homebox/lens/ui/screen/labeledit/LabelEditViewModel.kt"/>
|
||||||
|
<COMPONENT path="app/src/main/java/com/homebox/lens/ui/screen/labeledit/LabelEditScreen.kt"/>
|
||||||
|
<COMPONENT path="app/src/main/java/com/homebox/lens/ui/components/LoadingOverlay.kt"/>
|
||||||
|
<COMPONENT path="app/src/main/java/com/homebox/lens/ui/components/ColorPicker.kt"/>
|
||||||
|
<COMPONENT path="app/src/main/res/values/strings.xml"/>
|
||||||
|
<COMPONENT path="app/src/main/java/com/homebox/lens/navigation/Screen.kt"/>
|
||||||
|
<COMPONENT path="app/src/main/java/com/homebox/lens/navigation/NavGraph.kt"/>
|
||||||
|
<COMPONENT path="app/src/main/java/com/homebox/lens/ui/screen/labelslist/LabelsListScreen.kt"/>
|
||||||
|
</AFFECTED_COMPONENTS>
|
||||||
|
|
||||||
|
<VERIFICATION_STEPS>
|
||||||
|
<PREREQUISITES>
|
||||||
|
<STEP>Ensure the application is built and running on a device/emulator.</STEP>
|
||||||
|
</PREREQUISITES>
|
||||||
|
|
||||||
|
<SCENARIO id="SCN_001" name="Create New Label">
|
||||||
|
<STEP>Navigate to the Labels List screen.</STEP>
|
||||||
|
<STEP>Tap the "Add" (plus) Floating Action Button.</STEP>
|
||||||
|
<STEP>Verify that the "Create Label" screen appears.</STEP>
|
||||||
|
<STEP>Enter a label name (e.g., "My New Label") and select a color using the color picker.</STEP>
|
||||||
|
<STEP>Tap the "Save" button (check icon in top app bar).</STEP>
|
||||||
|
<STEP>Verify that the new label appears in the Labels List.</STEP>
|
||||||
|
</SCENARIO>
|
||||||
|
|
||||||
|
<SCENARIO id="SCN_002" name="Edit Existing Label">
|
||||||
|
<STEP>Navigate to the Labels List screen.</STEP>
|
||||||
|
<STEP>Tap on an existing label from the list.</STEP>
|
||||||
|
<STEP>Verify that the "Edit Label" screen appears with the label's current name and color pre-filled.</STEP>
|
||||||
|
<STEP>Modify the label name (e.g., "Updated Label") and/or color.</STEP>
|
||||||
|
<STEP>Tap the "Save" button (check icon in top app bar).</STEP>
|
||||||
|
<STEP>Verify that the label's changes are reflected in the Labels List.</STEP>
|
||||||
|
</SCENARIO>
|
||||||
|
|
||||||
|
<SCENARIO id="SCN_003" name="Validation - Empty Label Name">
|
||||||
|
<STEP>Navigate to the "Create Label" screen (via FAB).</STEP>
|
||||||
|
<STEP>Leave the label name field empty.</STEP>
|
||||||
|
<STEP>Tap the "Save" button.</STEP>
|
||||||
|
<STEP>Verify that an error message "Label name cannot be empty." is displayed below the name input field.</STEP>
|
||||||
|
<STEP>Verify that the label is NOT saved and the screen remains open.</STEP>
|
||||||
|
</SCENARIO>
|
||||||
|
|
||||||
|
<SCENARIO id="SCN_004" name="Navigation">
|
||||||
|
<STEP>From the Labels List screen, navigate to the "Create Label" screen.</STEP>
|
||||||
|
<STEP>Tap the "Back" arrow in the top app bar.</STEP>
|
||||||
|
<STEP>Verify that the app navigates back to the Labels List screen.</STEP>
|
||||||
|
<STEP>From the Labels List screen, tap on an existing label to go to the "Edit Label" screen.</STEP>
|
||||||
|
<STEP>Tap the "Back" arrow in the top app bar.</STEP>
|
||||||
|
<STEP>Verify that the app navigates back to the Labels List screen.</STEP>
|
||||||
|
</SCENARIO>
|
||||||
|
</VERIFICATION_STEPS>
|
||||||
|
</ASSURANCE_REPORT>
|
||||||
@@ -1,35 +1,71 @@
|
|||||||
<WORK_ORDERS>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<WORK_ORDER id="1" title="Implement Inventory List Screen">
|
<WORK_ORDER>
|
||||||
<INTENT_SPECIFICATION>
|
<METADATA>
|
||||||
Implement the UI for the inventory list screen in `InventoryListScreen.kt` to display a list of items using Jetpack Compose.
|
<WORK_ORDER_ID>20250906_100000</WORK_ORDER_ID>
|
||||||
Implement the `InventoryListViewModel.kt` to fetch a paginated list of items from the `ItemRepository` and expose it to the UI.
|
<TITLE>[ARCHITECT -> DEV] Implement Label Management Feature</TITLE>
|
||||||
The screen should show a loading indicator while data is being fetched and handle empty or error states.
|
<DESCRIPTION>
|
||||||
</INTENT_SPECIFICATION>
|
This work order is to implement the full lifecycle of label management,
|
||||||
<TARGET_FILES>
|
including creating, viewing, editing, and deleting labels.
|
||||||
<FILE>app/src/main/java/com/homebox/lens/ui/screen/inventorylist/InventoryListScreen.kt</FILE>
|
This involves creating a new screen for editing labels, a view model to handle the logic,
|
||||||
<FILE>app/src/main/java/com/homebox/lens/ui/screen/inventorylist/InventoryListViewModel.kt</FILE>
|
and integrating it with the existing label list screen.
|
||||||
</TARGET_FILES>
|
</DESCRIPTION>
|
||||||
|
<STATUS>Completed</STATUS>
|
||||||
|
<ASSIGNEE>agent-developer</ASSIGNEE>
|
||||||
|
<LABELS>
|
||||||
|
<LABEL>type::development</LABEL>
|
||||||
|
<LABEL>feature::label-management</LABEL>
|
||||||
|
</LABELS>
|
||||||
|
</METADATA>
|
||||||
|
|
||||||
|
<TASKS>
|
||||||
|
<TASK id="task_1" name="Create LabelEditViewModel">
|
||||||
|
<DESCRIPTION>
|
||||||
|
Create a new ViewModel `LabelEditViewModel.kt` in `app/src/main/java/com/homebox/lens/ui/screen/labeledit/`.
|
||||||
|
This ViewModel should handle the business logic for creating and updating a label.
|
||||||
|
It should use `GetLabelDetailsUseCase`, `CreateLabelUseCase`, and `UpdateLabelUseCase`.
|
||||||
|
</DESCRIPTION>
|
||||||
|
<CHECKLIST>
|
||||||
|
<ITEM>Create `app/src/main/java/com/homebox/lens/ui/screen/labeledit/LabelEditViewModel.kt`</ITEM>
|
||||||
|
<ITEM>Inject `GetLabelDetailsUseCase`, `CreateLabelUseCase`, `UpdateLabelUseCase`.</ITEM>
|
||||||
|
<ITEM>Implement state management for the label editing screen.</ITEM>
|
||||||
|
<ITEM>Implement methods to create and update a label.</ITEM>
|
||||||
|
</CHECKLIST>
|
||||||
|
</TASK>
|
||||||
|
|
||||||
|
<TASK id="task_2" name="Create LabelEditScreen">
|
||||||
|
<DESCRIPTION>
|
||||||
|
Create a new Jetpack Compose screen `LabelEditScreen.kt` in `app/src/main/java/com/homebox/lens/ui/screen/labeledit/`.
|
||||||
|
This screen will be used for both creating a new label and editing an existing one.
|
||||||
|
The UI should be similar to the `LocationEditScreen`.
|
||||||
|
</DESCRIPTION>
|
||||||
|
<CHECKLIST>
|
||||||
|
<ITEM>Create `app/src/main/java/com/homebox/lens/ui/screen/labeledit/LabelEditScreen.kt`</ITEM>
|
||||||
|
<ITEM>Implement the UI for creating/editing a label (e.g., a text field for the name and a color picker).</ITEM>
|
||||||
|
<ITEM>Connect the screen to `LabelEditViewModel`.</ITEM>
|
||||||
|
</CHECKLIST>
|
||||||
|
</TASK>
|
||||||
|
|
||||||
|
<TASK id="task_3" name="Update Navigation">
|
||||||
|
<DESCRIPTION>
|
||||||
|
Update the navigation graph to include the new `LabelEditScreen`.
|
||||||
|
The `LabelsListScreen` should navigate to `LabelEditScreen` when the user wants to create or edit a label.
|
||||||
|
</DESCRIPTION>
|
||||||
|
<CHECKLIST>
|
||||||
|
<ITEM>Add a route for `LabelEditScreen` in `Screen.kt`.</ITEM>
|
||||||
|
<ITEM>Add the new screen to the `NavGraph.kt`.</ITEM>
|
||||||
|
<ITEM>Implement navigation from `LabelsListScreen` to `LabelEditScreen`.</ITEM>
|
||||||
|
</CHECKLIST>
|
||||||
|
</TASK>
|
||||||
|
|
||||||
|
<TASK id="task_4" name="Create GetLabelDetailsUseCase">
|
||||||
|
<DESCRIPTION>
|
||||||
|
Create a new UseCase `GetLabelDetailsUseCase.kt` in `domain/src/main/java/com/homebox/lens/domain/usecase/`.
|
||||||
|
This UseCase will be responsible for getting the details of a single label.
|
||||||
|
</DESCRIPTION>
|
||||||
|
<CHECKLIST>
|
||||||
|
<ITEM>Create `domain/src/main/java/com/homebox/lens/domain/usecase/GetLabelDetailsUseCase.kt`</ITEM>
|
||||||
|
<ITEM>Implement the logic to get label details from the `ItemRepository`.</ITEM>
|
||||||
|
</CHECKLIST>
|
||||||
|
</TASK>
|
||||||
|
</TASKS>
|
||||||
</WORK_ORDER>
|
</WORK_ORDER>
|
||||||
<WORK_ORDER id="2" title="Implement Item Details Screen">
|
|
||||||
<INTENT_SPECIFICATION>
|
|
||||||
Implement the UI for the item details screen in `ItemDetailsScreen.kt`. It should display all the information about a specific item.
|
|
||||||
Implement the `ItemDetailsViewModel.kt` to fetch the details of a single item from the `ItemRepository` using its ID.
|
|
||||||
The screen should handle cases where the item is not found.
|
|
||||||
</INTENT_SPECIFICATION>
|
|
||||||
<TARGET_FILES>
|
|
||||||
<FILE>app/src/main/java/com/homebox/lens/ui/screen/itemdetails/ItemDetailsScreen.kt</FILE>
|
|
||||||
<FILE>app/src/main/java/com/homebox/lens/ui/screen/itemdetails/ItemDetailsViewModel.kt</FILE>
|
|
||||||
</TARGET_FILES>
|
|
||||||
</WORK_ORDER>
|
|
||||||
<WORK_ORDER id="3" title="Implement Search Screen">
|
|
||||||
<INTENT_SPECIFICATION>
|
|
||||||
Implement the UI for the search screen in `SearchScreen.kt`. It should contain a search bar and a list to display search results.
|
|
||||||
Implement the `SearchViewModel.kt` to take a search query, call the `SearchItemsUseCase`, and expose the results to the UI.
|
|
||||||
The search should be triggered as the user types, with debouncing to avoid excessive API calls.
|
|
||||||
</INTENT_SPECIFICATION>
|
|
||||||
<TARGET_FILES>
|
|
||||||
<FILE>app/src/main/java/com/homebox/lens/ui/screen/search/SearchScreen.kt</FILE>
|
|
||||||
<FILE>app/src/main/java/com/homebox/lens/ui/screen/search/SearchViewModel.kt</FILE>
|
|
||||||
</TARGET_FILES>
|
|
||||||
</WORK_ORDER>
|
|
||||||
</WORK_ORDERS>
|
|
||||||
Reference in New Issue
Block a user