fenix-mcp 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fenix_mcp/__init__.py +4 -1
- fenix_mcp/application/tool_base.py +3 -2
- fenix_mcp/application/tool_registry.py +3 -1
- fenix_mcp/application/tools/health.py +1 -3
- fenix_mcp/application/tools/initialize.py +18 -6
- fenix_mcp/application/tools/intelligence.py +47 -15
- fenix_mcp/application/tools/knowledge.py +162 -50
- fenix_mcp/application/tools/productivity.py +26 -9
- fenix_mcp/application/tools/user_config.py +18 -9
- fenix_mcp/domain/initialization.py +21 -9
- fenix_mcp/domain/intelligence.py +27 -15
- fenix_mcp/domain/knowledge.py +161 -61
- fenix_mcp/domain/productivity.py +3 -2
- fenix_mcp/domain/user_config.py +16 -7
- fenix_mcp/infrastructure/config.py +6 -2
- fenix_mcp/infrastructure/context.py +0 -1
- fenix_mcp/infrastructure/fenix_api/client.py +118 -38
- fenix_mcp/infrastructure/http_client.py +1 -1
- fenix_mcp/interface/mcp_server.py +0 -3
- fenix_mcp/interface/transports.py +19 -10
- fenix_mcp/main.py +4 -1
- {fenix_mcp-0.1.0.dist-info → fenix_mcp-0.2.1.dist-info}/METADATA +56 -6
- fenix_mcp-0.2.1.dist-info/RECORD +29 -0
- fenix_mcp-0.1.0.dist-info/RECORD +0 -29
- {fenix_mcp-0.1.0.dist-info → fenix_mcp-0.2.1.dist-info}/WHEEL +0 -0
- {fenix_mcp-0.1.0.dist-info → fenix_mcp-0.2.1.dist-info}/entry_points.txt +0 -0
- {fenix_mcp-0.1.0.dist-info → fenix_mcp-0.2.1.dist-info}/top_level.txt +0 -0
fenix_mcp/__init__.py
CHANGED
|
@@ -10,8 +10,11 @@ This package follows a Clean Architecture layout inside the MCP ecosystem:
|
|
|
10
10
|
- application: tools, registries, presenters and use-case orchestrators
|
|
11
11
|
- domain: pure business models and services
|
|
12
12
|
- infrastructure: API clients, config, logging and shared context
|
|
13
|
+
|
|
14
|
+
Version 0.1.0 - Initial release with basic MCP functionality.
|
|
15
|
+
Updated with improved error handling and better documentation.
|
|
13
16
|
"""
|
|
14
17
|
|
|
15
18
|
__all__ = ["__version__"]
|
|
16
19
|
|
|
17
|
-
__version__ = "0.1
|
|
20
|
+
__version__ = "0.2.1"
|
|
@@ -35,7 +35,9 @@ class Tool(ABC):
|
|
|
35
35
|
"inputSchema": self.request_model.model_json_schema(),
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
async def execute(
|
|
38
|
+
async def execute(
|
|
39
|
+
self, raw_arguments: Dict[str, Any], context: AppContext
|
|
40
|
+
) -> ToolResponse:
|
|
39
41
|
"""Validate raw arguments and run the tool."""
|
|
40
42
|
payload = self.request_model.model_validate(raw_arguments or {})
|
|
41
43
|
return await self.run(payload, context)
|
|
@@ -43,4 +45,3 @@ class Tool(ABC):
|
|
|
43
45
|
@abstractmethod
|
|
44
46
|
async def run(self, payload: ToolRequest, context: AppContext) -> ToolResponse:
|
|
45
47
|
"""Execute business logic and return a MCP-formatted response."""
|
|
46
|
-
|
|
@@ -22,7 +22,9 @@ class ToolRegistry:
|
|
|
22
22
|
def list_definitions(self) -> List[dict]:
|
|
23
23
|
return [tool.schema() for tool in self._tools.values()]
|
|
24
24
|
|
|
25
|
-
async def execute(
|
|
25
|
+
async def execute(
|
|
26
|
+
self, name: str, arguments: dict, context: AppContext
|
|
27
|
+
) -> ToolResponse:
|
|
26
28
|
try:
|
|
27
29
|
tool = self._tools[name]
|
|
28
30
|
except KeyError as exc:
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
from fenix_mcp.application.presenters import key_value, text
|
|
6
|
+
from fenix_mcp.application.presenters import key_value
|
|
9
7
|
from fenix_mcp.application.tool_base import Tool, ToolRequest
|
|
10
8
|
from fenix_mcp.infrastructure.context import AppContext
|
|
11
9
|
|
|
@@ -21,26 +21,35 @@ class InitializeAction(str, Enum):
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class InitializeRequest(ToolRequest):
|
|
24
|
-
action: InitializeAction = Field(
|
|
24
|
+
action: InitializeAction = Field(
|
|
25
|
+
description="Operação de inicialização a executar."
|
|
26
|
+
)
|
|
25
27
|
include_user_docs: bool = Field(
|
|
26
28
|
default=True,
|
|
27
|
-
description=
|
|
29
|
+
description=(
|
|
30
|
+
"Inclui documentos pessoais durante a inicialização "
|
|
31
|
+
"(apenas para ação init)."
|
|
32
|
+
),
|
|
28
33
|
)
|
|
29
34
|
limit: int = Field(
|
|
30
35
|
default=50,
|
|
31
36
|
ge=1,
|
|
32
37
|
le=200,
|
|
33
|
-
description="Quantidade máxima de documentos principais/pessoais carregados.",
|
|
38
|
+
description=("Quantidade máxima de documentos principais/pessoais carregados."),
|
|
34
39
|
)
|
|
35
40
|
answers: Optional[List[str]] = Field(
|
|
36
41
|
default=None,
|
|
37
|
-
description=
|
|
42
|
+
description=(
|
|
43
|
+
"Lista com 9 respostas textuais para processar o setup personalizado."
|
|
44
|
+
),
|
|
38
45
|
)
|
|
39
46
|
|
|
40
47
|
|
|
41
48
|
class InitializeTool(Tool):
|
|
42
49
|
name = "initialize"
|
|
43
|
-
description =
|
|
50
|
+
description = (
|
|
51
|
+
"Inicializa o ambiente do Fênix Cloud ou processa o setup personalizado."
|
|
52
|
+
)
|
|
44
53
|
request_model = InitializeRequest
|
|
45
54
|
|
|
46
55
|
def __init__(self, context: AppContext):
|
|
@@ -62,7 +71,10 @@ class InitializeTool(Tool):
|
|
|
62
71
|
)
|
|
63
72
|
except Exception as exc: # pragma: no cover - defensive
|
|
64
73
|
self._context.logger.error("Initialize failed: %s", exc)
|
|
65
|
-
return text(
|
|
74
|
+
return text(
|
|
75
|
+
"❌ Falha ao carregar dados de inicialização. "
|
|
76
|
+
"Verifique se o token tem acesso à API."
|
|
77
|
+
)
|
|
66
78
|
|
|
67
79
|
if (
|
|
68
80
|
not data.core_documents
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from typing import Any, Dict,
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
8
|
|
|
9
9
|
from pydantic import Field
|
|
10
10
|
|
|
@@ -21,10 +21,16 @@ class IntelligenceAction(str, Enum):
|
|
|
21
21
|
obj.description = description
|
|
22
22
|
return obj
|
|
23
23
|
|
|
24
|
-
SMART_CREATE = (
|
|
24
|
+
SMART_CREATE = (
|
|
25
|
+
"memory_smart_create",
|
|
26
|
+
"Cria memórias inteligentes com análise de similaridade.",
|
|
27
|
+
)
|
|
25
28
|
QUERY = ("memory_query", "Lista memórias aplicando filtros e busca textual.")
|
|
26
29
|
SIMILARITY = ("memory_similarity", "Busca memórias similares a um conteúdo base.")
|
|
27
|
-
CONSOLIDATE = (
|
|
30
|
+
CONSOLIDATE = (
|
|
31
|
+
"memory_consolidate",
|
|
32
|
+
"Consolida múltiplas memórias em uma principal.",
|
|
33
|
+
)
|
|
28
34
|
PRIORITY = ("memory_priority", "Retorna memórias ordenadas por prioridade.")
|
|
29
35
|
ANALYTICS = ("memory_analytics", "Calcula métricas e analytics das memórias.")
|
|
30
36
|
UPDATE = ("memory_update", "Atualiza campos de uma memória existente.")
|
|
@@ -47,17 +53,24 @@ class IntelligenceAction(str, Enum):
|
|
|
47
53
|
|
|
48
54
|
ACTION_FIELD_DESCRIPTION = (
|
|
49
55
|
"Ação de inteligência a executar. Use um dos valores: "
|
|
50
|
-
+ ", ".join(
|
|
56
|
+
+ ", ".join(
|
|
57
|
+
f"`{member.value}` ({member.description.rstrip('.')})."
|
|
58
|
+
for member in IntelligenceAction
|
|
59
|
+
)
|
|
51
60
|
)
|
|
52
61
|
|
|
53
62
|
|
|
54
63
|
class IntelligenceRequest(ToolRequest):
|
|
55
64
|
action: IntelligenceAction = Field(description=ACTION_FIELD_DESCRIPTION)
|
|
56
65
|
title: Optional[str] = Field(default=None, description="Título da memória.")
|
|
57
|
-
content: Optional[str] = Field(
|
|
66
|
+
content: Optional[str] = Field(
|
|
67
|
+
default=None, description="Conteúdo/texto da memória."
|
|
68
|
+
)
|
|
58
69
|
context: Optional[str] = Field(default=None, description="Contexto adicional.")
|
|
59
70
|
source: Optional[str] = Field(default=None, description="Fonte da memória.")
|
|
60
|
-
importance: str = Field(
|
|
71
|
+
importance: str = Field(
|
|
72
|
+
default="medium", description="Nível de importância da memória."
|
|
73
|
+
)
|
|
61
74
|
tags: Optional[List[str]] = Field(default=None, description="Tags da memória.")
|
|
62
75
|
limit: int = Field(default=20, ge=1, le=100, description="Limite de resultados.")
|
|
63
76
|
offset: int = Field(default=0, ge=0, description="Offset para paginação.")
|
|
@@ -65,23 +78,39 @@ class IntelligenceRequest(ToolRequest):
|
|
|
65
78
|
category: Optional[str] = Field(default=None, description="Categoria para filtro.")
|
|
66
79
|
date_from: Optional[str] = Field(default=None, description="Filtro inicial (ISO).")
|
|
67
80
|
date_to: Optional[str] = Field(default=None, description="Filtro final (ISO).")
|
|
68
|
-
threshold: float = Field(
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
threshold: float = Field(
|
|
82
|
+
default=0.8, ge=0, le=1, description="Limite mínimo de similaridade."
|
|
83
|
+
)
|
|
84
|
+
max_results: int = Field(
|
|
85
|
+
default=5, ge=1, le=20, description="Máximo de memórias similares."
|
|
86
|
+
)
|
|
87
|
+
memory_ids: Optional[List[str]] = Field(
|
|
88
|
+
default=None, description="IDs para consolidação."
|
|
89
|
+
)
|
|
71
90
|
strategy: str = Field(default="merge", description="Estratégia de consolidação.")
|
|
72
|
-
time_range: str = Field(
|
|
91
|
+
time_range: str = Field(
|
|
92
|
+
default="month", description="Janela de tempo para analytics."
|
|
93
|
+
)
|
|
73
94
|
group_by: str = Field(default="category", description="Agrupamento para analytics.")
|
|
74
95
|
id: Optional[str] = Field(default=None, description="ID da memória para update.")
|
|
75
|
-
documentation_item_id: Optional[str] = Field(
|
|
96
|
+
documentation_item_id: Optional[str] = Field(
|
|
97
|
+
default=None, description="ID de documentação relacionada."
|
|
98
|
+
)
|
|
76
99
|
mode_id: Optional[str] = Field(default=None, description="ID do modo relacionado.")
|
|
77
100
|
rule_id: Optional[str] = Field(default=None, description="ID da regra relacionada.")
|
|
78
|
-
work_item_id: Optional[str] = Field(
|
|
79
|
-
|
|
101
|
+
work_item_id: Optional[str] = Field(
|
|
102
|
+
default=None, description="ID do work item relacionado."
|
|
103
|
+
)
|
|
104
|
+
sprint_id: Optional[str] = Field(
|
|
105
|
+
default=None, description="ID do sprint relacionado."
|
|
106
|
+
)
|
|
80
107
|
|
|
81
108
|
|
|
82
109
|
class IntelligenceTool(Tool):
|
|
83
110
|
name = "intelligence"
|
|
84
|
-
description =
|
|
111
|
+
description = (
|
|
112
|
+
"Operações de inteligência do Fênix Cloud (memórias e smart operations)."
|
|
113
|
+
)
|
|
85
114
|
request_model = IntelligenceRequest
|
|
86
115
|
|
|
87
116
|
def __init__(self, context: AppContext):
|
|
@@ -231,7 +260,10 @@ class IntelligenceTool(Tool):
|
|
|
231
260
|
)
|
|
232
261
|
|
|
233
262
|
async def _handle_help(self):
|
|
234
|
-
return text(
|
|
263
|
+
return text(
|
|
264
|
+
"📚 **Ações disponíveis para intelligence**\n\n"
|
|
265
|
+
+ IntelligenceAction.formatted_help()
|
|
266
|
+
)
|
|
235
267
|
|
|
236
268
|
|
|
237
269
|
def _format_memory(memory: Dict[str, Any]) -> str:
|
|
@@ -10,7 +10,7 @@ from pydantic import Field
|
|
|
10
10
|
|
|
11
11
|
from fenix_mcp.application.presenters import text
|
|
12
12
|
from fenix_mcp.application.tool_base import Tool, ToolRequest
|
|
13
|
-
from fenix_mcp.domain.knowledge import KnowledgeService, _format_date
|
|
13
|
+
from fenix_mcp.domain.knowledge import KnowledgeService, _format_date
|
|
14
14
|
from fenix_mcp.infrastructure.context import AppContext
|
|
15
15
|
|
|
16
16
|
|
|
@@ -22,10 +22,19 @@ class KnowledgeAction(str, Enum):
|
|
|
22
22
|
return obj
|
|
23
23
|
|
|
24
24
|
# Work items
|
|
25
|
-
WORK_CREATE = (
|
|
26
|
-
|
|
25
|
+
WORK_CREATE = (
|
|
26
|
+
"work_create",
|
|
27
|
+
"Cria um work item com título, status e vínculos opcionais.",
|
|
28
|
+
)
|
|
29
|
+
WORK_LIST = (
|
|
30
|
+
"work_list",
|
|
31
|
+
"Lista work items com filtros de status, prioridade e contexto.",
|
|
32
|
+
)
|
|
27
33
|
WORK_GET = ("work_get", "Obtém detalhes completos de um work item pelo ID.")
|
|
28
|
-
WORK_UPDATE = (
|
|
34
|
+
WORK_UPDATE = (
|
|
35
|
+
"work_update",
|
|
36
|
+
"Atualiza campos específicos de um work item existente.",
|
|
37
|
+
)
|
|
29
38
|
WORK_DELETE = ("work_delete", "Remove um work item definitivamente.")
|
|
30
39
|
WORK_BACKLOG = ("work_backlog", "Lista itens do backlog de um time.")
|
|
31
40
|
WORK_SEARCH = ("work_search", "Busca work items por texto com filtros adicionais.")
|
|
@@ -45,7 +54,10 @@ class KnowledgeAction(str, Enum):
|
|
|
45
54
|
SPRINT_BY_TEAM = ("sprint_by_team", "Lista sprints associados a um time.")
|
|
46
55
|
SPRINT_ACTIVE = ("sprint_active", "Obtém o sprint ativo de um time.")
|
|
47
56
|
SPRINT_GET = ("sprint_get", "Obtém detalhes de um sprint pelo ID.")
|
|
48
|
-
SPRINT_WORK_ITEMS = (
|
|
57
|
+
SPRINT_WORK_ITEMS = (
|
|
58
|
+
"sprint_work_items",
|
|
59
|
+
"Lista work items vinculados a um sprint.",
|
|
60
|
+
)
|
|
49
61
|
|
|
50
62
|
# Modes
|
|
51
63
|
MODE_CREATE = ("mode_create", "Cria um modo com conteúdo e metadados opcionais.")
|
|
@@ -54,7 +66,10 @@ class KnowledgeAction(str, Enum):
|
|
|
54
66
|
MODE_UPDATE = ("mode_update", "Atualiza propriedades de um modo existente.")
|
|
55
67
|
MODE_DELETE = ("mode_delete", "Remove um modo.")
|
|
56
68
|
MODE_RULE_ADD = ("mode_rule_add", "Associa uma regra a um modo.")
|
|
57
|
-
MODE_RULE_REMOVE = (
|
|
69
|
+
MODE_RULE_REMOVE = (
|
|
70
|
+
"mode_rule_remove",
|
|
71
|
+
"Remove a associação de uma regra com um modo.",
|
|
72
|
+
)
|
|
58
73
|
MODE_RULES = ("mode_rules", "Lista regras associadas a um modo.")
|
|
59
74
|
|
|
60
75
|
# Rules
|
|
@@ -99,9 +114,9 @@ class KnowledgeAction(str, Enum):
|
|
|
99
114
|
return "\n".join(lines)
|
|
100
115
|
|
|
101
116
|
|
|
102
|
-
ACTION_FIELD_DESCRIPTION = (
|
|
103
|
-
"
|
|
104
|
-
|
|
117
|
+
ACTION_FIELD_DESCRIPTION = "Ação de conhecimento. Escolha um dos valores: " + ", ".join(
|
|
118
|
+
f"`{member.value}` ({member.description.rstrip('.')})."
|
|
119
|
+
for member in KnowledgeAction
|
|
105
120
|
)
|
|
106
121
|
|
|
107
122
|
|
|
@@ -117,22 +132,38 @@ class KnowledgeRequest(ToolRequest):
|
|
|
117
132
|
action: KnowledgeAction = Field(description=ACTION_FIELD_DESCRIPTION)
|
|
118
133
|
id: Optional[str] = Field(default=None, description="ID principal do recurso.")
|
|
119
134
|
limit: int = Field(default=20, ge=1, le=100, description="Limite de resultados.")
|
|
120
|
-
offset: int = Field(
|
|
121
|
-
|
|
135
|
+
offset: int = Field(
|
|
136
|
+
default=0, ge=0, description="Offset de paginação (quando suportado)."
|
|
137
|
+
)
|
|
138
|
+
team_id: Optional[str] = Field(
|
|
139
|
+
default=None, description="ID do time para filtros (boards/sprints/docs)."
|
|
140
|
+
)
|
|
122
141
|
board_id: Optional[str] = Field(default=None, description="ID do board associado.")
|
|
123
|
-
sprint_id: Optional[str] = Field(
|
|
142
|
+
sprint_id: Optional[str] = Field(
|
|
143
|
+
default=None, description="ID do sprint associado."
|
|
144
|
+
)
|
|
124
145
|
epic_id: Optional[str] = Field(default=None, description="ID do épico associado.")
|
|
125
146
|
query: Optional[str] = Field(default=None, description="Filtro/busca.")
|
|
126
|
-
return_content: Optional[bool] = Field(
|
|
127
|
-
|
|
128
|
-
|
|
147
|
+
return_content: Optional[bool] = Field(
|
|
148
|
+
default=None, description="Retorna conteúdo completo."
|
|
149
|
+
)
|
|
150
|
+
return_description: Optional[bool] = Field(
|
|
151
|
+
default=None, description="Retorna descrição completa."
|
|
152
|
+
)
|
|
153
|
+
return_metadata: Optional[bool] = Field(
|
|
154
|
+
default=None, description="Retorna metadados completos."
|
|
155
|
+
)
|
|
129
156
|
|
|
130
157
|
# Work item fields
|
|
131
158
|
work_title: Optional[str] = Field(default=None, description="Título do work item.")
|
|
132
|
-
work_description: Optional[str] = Field(
|
|
159
|
+
work_description: Optional[str] = Field(
|
|
160
|
+
default=None, description="Descrição do work item."
|
|
161
|
+
)
|
|
133
162
|
work_type: Optional[str] = Field(default="task", description="Tipo do work item.")
|
|
134
163
|
work_status: Optional[str] = Field(default=None, description="Status do work item.")
|
|
135
|
-
work_priority: Optional[str] = Field(
|
|
164
|
+
work_priority: Optional[str] = Field(
|
|
165
|
+
default=None, description="Prioridade do work item."
|
|
166
|
+
)
|
|
136
167
|
story_points: Optional[int] = Field(default=None, description="Story points.")
|
|
137
168
|
assignee_id: Optional[str] = Field(default=None, description="ID do responsável.")
|
|
138
169
|
parent_id: Optional[str] = Field(default=None, description="ID do item pai.")
|
|
@@ -140,34 +171,56 @@ class KnowledgeRequest(ToolRequest):
|
|
|
140
171
|
# Mode fields
|
|
141
172
|
mode_id: Optional[str] = Field(default=None, description="ID do modo relacionado.")
|
|
142
173
|
mode_name: Optional[str] = Field(default=None, description="Nome do modo.")
|
|
143
|
-
mode_description: Optional[str] = Field(
|
|
174
|
+
mode_description: Optional[str] = Field(
|
|
175
|
+
default=None, description="Descrição do modo."
|
|
176
|
+
)
|
|
144
177
|
mode_content: Optional[str] = Field(default=None, description="Conteúdo do modo.")
|
|
145
|
-
mode_is_default: Optional[bool] = Field(
|
|
178
|
+
mode_is_default: Optional[bool] = Field(
|
|
179
|
+
default=None, description="Indica se o modo é padrão."
|
|
180
|
+
)
|
|
146
181
|
|
|
147
182
|
# Rule fields
|
|
148
183
|
rule_id: Optional[str] = Field(default=None, description="ID da regra relacionada.")
|
|
149
184
|
rule_name: Optional[str] = Field(default=None, description="Nome da regra.")
|
|
150
|
-
rule_description: Optional[str] = Field(
|
|
185
|
+
rule_description: Optional[str] = Field(
|
|
186
|
+
default=None, description="Descrição da regra."
|
|
187
|
+
)
|
|
151
188
|
rule_content: Optional[str] = Field(default=None, description="Conteúdo da regra.")
|
|
152
189
|
rule_is_default: Optional[bool] = Field(default=None, description="Regra padrão.")
|
|
153
190
|
|
|
154
191
|
# Documentation fields
|
|
155
|
-
doc_title: Optional[str] = Field(
|
|
192
|
+
doc_title: Optional[str] = Field(
|
|
193
|
+
default=None, description="Título da documentação."
|
|
194
|
+
)
|
|
156
195
|
doc_description: Optional[str] = Field(default=None, description="Descrição.")
|
|
157
|
-
doc_content: Optional[str] = Field(
|
|
158
|
-
|
|
196
|
+
doc_content: Optional[str] = Field(
|
|
197
|
+
default=None, description="Conteúdo da documentação."
|
|
198
|
+
)
|
|
199
|
+
doc_status: Optional[str] = Field(
|
|
200
|
+
default=None, description="Status da documentação."
|
|
201
|
+
)
|
|
159
202
|
doc_type: Optional[str] = Field(default=None, description="Tipo da documentação.")
|
|
160
|
-
doc_language: Optional[str] = Field(
|
|
203
|
+
doc_language: Optional[str] = Field(
|
|
204
|
+
default=None, description="Idioma da documentação."
|
|
205
|
+
)
|
|
161
206
|
doc_parent_id: Optional[str] = Field(default=None, description="Documento pai.")
|
|
162
|
-
doc_team_id: Optional[str] = Field(
|
|
207
|
+
doc_team_id: Optional[str] = Field(
|
|
208
|
+
default=None, description="Time responsável pela documentação."
|
|
209
|
+
)
|
|
163
210
|
doc_owner_id: Optional[str] = Field(default=None, description="ID do dono.")
|
|
164
211
|
doc_reviewer_id: Optional[str] = Field(default=None, description="ID do revisor.")
|
|
165
212
|
doc_version: Optional[str] = Field(default=None, description="Versão.")
|
|
166
213
|
doc_category: Optional[str] = Field(default=None, description="Categoria.")
|
|
167
214
|
doc_tags: Optional[List[str]] = Field(default=None, description="Tags.")
|
|
168
|
-
doc_position: Optional[int] = Field(
|
|
169
|
-
|
|
170
|
-
|
|
215
|
+
doc_position: Optional[int] = Field(
|
|
216
|
+
default=None, description="Posição desejada ao mover documentos."
|
|
217
|
+
)
|
|
218
|
+
doc_emoji: Optional[str] = Field(
|
|
219
|
+
default=None, description="Emoji exibido junto ao documento."
|
|
220
|
+
)
|
|
221
|
+
doc_emote: Optional[str] = Field(
|
|
222
|
+
default=None, description="Alias para emoji, mantido por compatibilidade."
|
|
223
|
+
)
|
|
171
224
|
|
|
172
225
|
|
|
173
226
|
class KnowledgeTool(Tool):
|
|
@@ -323,7 +376,11 @@ class KnowledgeTool(Tool):
|
|
|
323
376
|
|
|
324
377
|
return text(
|
|
325
378
|
"❌ Ação de work item não suportada.\n\nEscolha um dos valores:\n"
|
|
326
|
-
+ "\n".join(
|
|
379
|
+
+ "\n".join(
|
|
380
|
+
f"- `{value}`"
|
|
381
|
+
for value in KnowledgeAction.choices()
|
|
382
|
+
if value.startswith("work_")
|
|
383
|
+
)
|
|
327
384
|
)
|
|
328
385
|
|
|
329
386
|
# ------------------------------------------------------------------
|
|
@@ -332,7 +389,9 @@ class KnowledgeTool(Tool):
|
|
|
332
389
|
async def _run_board(self, payload: KnowledgeRequest):
|
|
333
390
|
action = payload.action
|
|
334
391
|
if action is KnowledgeAction.BOARD_LIST:
|
|
335
|
-
boards = await self._service.board_list(
|
|
392
|
+
boards = await self._service.board_list(
|
|
393
|
+
limit=payload.limit, offset=payload.offset
|
|
394
|
+
)
|
|
336
395
|
if not boards:
|
|
337
396
|
return text("🗂️ Nenhum board encontrado.")
|
|
338
397
|
body = "\n\n".join(_format_board(board) for board in boards)
|
|
@@ -366,12 +425,19 @@ class KnowledgeTool(Tool):
|
|
|
366
425
|
columns = await self._service.board_columns(payload.board_id)
|
|
367
426
|
if not columns:
|
|
368
427
|
return text("📊 Board sem colunas cadastradas.")
|
|
369
|
-
body = "\n".join(
|
|
428
|
+
body = "\n".join(
|
|
429
|
+
f"- {col.get('name', 'Sem nome')} (ID: {col.get('id')})"
|
|
430
|
+
for col in columns
|
|
431
|
+
)
|
|
370
432
|
return text(f"📊 **Colunas do board:**\n{body}")
|
|
371
433
|
|
|
372
434
|
return text(
|
|
373
435
|
"❌ Ação de board não suportada.\n\nEscolha um dos valores:\n"
|
|
374
|
-
+ "\n".join(
|
|
436
|
+
+ "\n".join(
|
|
437
|
+
f"- `{value}`"
|
|
438
|
+
for value in KnowledgeAction.choices()
|
|
439
|
+
if value.startswith("board_")
|
|
440
|
+
)
|
|
375
441
|
)
|
|
376
442
|
|
|
377
443
|
# ------------------------------------------------------------------
|
|
@@ -380,7 +446,9 @@ class KnowledgeTool(Tool):
|
|
|
380
446
|
async def _run_sprint(self, payload: KnowledgeRequest):
|
|
381
447
|
action = payload.action
|
|
382
448
|
if action is KnowledgeAction.SPRINT_LIST:
|
|
383
|
-
sprints = await self._service.sprint_list(
|
|
449
|
+
sprints = await self._service.sprint_list(
|
|
450
|
+
limit=payload.limit, offset=payload.offset
|
|
451
|
+
)
|
|
384
452
|
if not sprints:
|
|
385
453
|
return text("🏃 Nenhum sprint encontrado.")
|
|
386
454
|
body = "\n\n".join(_format_sprint(sprint) for sprint in sprints)
|
|
@@ -420,7 +488,11 @@ class KnowledgeTool(Tool):
|
|
|
420
488
|
|
|
421
489
|
return text(
|
|
422
490
|
"❌ Ação de sprint não suportada.\n\nEscolha um dos valores:\n"
|
|
423
|
-
+ "\n".join(
|
|
491
|
+
+ "\n".join(
|
|
492
|
+
f"- `{value}`"
|
|
493
|
+
for value in KnowledgeAction.choices()
|
|
494
|
+
if value.startswith("sprint_")
|
|
495
|
+
)
|
|
424
496
|
)
|
|
425
497
|
|
|
426
498
|
# ------------------------------------------------------------------
|
|
@@ -514,13 +586,18 @@ class KnowledgeTool(Tool):
|
|
|
514
586
|
if not rules:
|
|
515
587
|
return text("🔗 Nenhuma associação encontrada.")
|
|
516
588
|
body = "\n".join(
|
|
517
|
-
f"- {item.get('name', 'Sem nome')} (ID: {item.get('id')})"
|
|
589
|
+
f"- {item.get('name', 'Sem nome')} (ID: {item.get('id')})"
|
|
590
|
+
for item in rules
|
|
518
591
|
)
|
|
519
592
|
return text(f"🔗 **Associações para {context_label}:**\n{body}")
|
|
520
593
|
|
|
521
594
|
return text(
|
|
522
595
|
"❌ Ação de modo não suportada.\n\nEscolha um dos valores:\n"
|
|
523
|
-
+ "\n".join(
|
|
596
|
+
+ "\n".join(
|
|
597
|
+
f"- `{value}`"
|
|
598
|
+
for value in KnowledgeAction.choices()
|
|
599
|
+
if value.startswith("mode_")
|
|
600
|
+
)
|
|
524
601
|
)
|
|
525
602
|
|
|
526
603
|
# ------------------------------------------------------------------
|
|
@@ -585,7 +662,11 @@ class KnowledgeTool(Tool):
|
|
|
585
662
|
|
|
586
663
|
return text(
|
|
587
664
|
"❌ Ação de regra não suportada.\n\nEscolha um dos valores:\n"
|
|
588
|
-
+ "\n".join(
|
|
665
|
+
+ "\n".join(
|
|
666
|
+
f"- `{value}`"
|
|
667
|
+
for value in KnowledgeAction.choices()
|
|
668
|
+
if value.startswith("rule_")
|
|
669
|
+
)
|
|
589
670
|
)
|
|
590
671
|
|
|
591
672
|
# ------------------------------------------------------------------
|
|
@@ -685,17 +766,24 @@ class KnowledgeTool(Tool):
|
|
|
685
766
|
limit=payload.limit,
|
|
686
767
|
)
|
|
687
768
|
if not docs:
|
|
688
|
-
return text(
|
|
769
|
+
return text(
|
|
770
|
+
"🔍 Nenhum documento encontrado para os filtros informados."
|
|
771
|
+
)
|
|
689
772
|
body = "\n\n".join(_format_doc(doc) for doc in docs)
|
|
690
773
|
return text(f"🔍 **Resultados ({len(docs)}):**\n\n{body}")
|
|
691
774
|
|
|
692
775
|
if action is KnowledgeAction.DOC_ROOTS:
|
|
693
776
|
if not (payload.doc_team_id or payload.team_id):
|
|
694
777
|
return text("❌ Informe team_id para listar raízes.")
|
|
695
|
-
docs = await self._service.doc_roots(
|
|
778
|
+
docs = await self._service.doc_roots(
|
|
779
|
+
team_id=payload.doc_team_id or payload.team_id
|
|
780
|
+
)
|
|
696
781
|
if not docs:
|
|
697
782
|
return text("📚 Nenhuma raiz encontrada.")
|
|
698
|
-
body = "\n".join(
|
|
783
|
+
body = "\n".join(
|
|
784
|
+
f"- {doc.get('title', 'Sem título')} (ID: {doc.get('id')})"
|
|
785
|
+
for doc in docs
|
|
786
|
+
)
|
|
699
787
|
return text(f"📚 **Raízes de documentação:**\n{body}")
|
|
700
788
|
|
|
701
789
|
if action is KnowledgeAction.DOC_RECENT:
|
|
@@ -713,7 +801,9 @@ class KnowledgeTool(Tool):
|
|
|
713
801
|
if action is KnowledgeAction.DOC_ANALYTICS:
|
|
714
802
|
if not (payload.doc_team_id or payload.team_id):
|
|
715
803
|
return text("❌ Informe team_id para obter analytics.")
|
|
716
|
-
analytics = await self._service.doc_analytics(
|
|
804
|
+
analytics = await self._service.doc_analytics(
|
|
805
|
+
team_id=payload.doc_team_id or payload.team_id
|
|
806
|
+
)
|
|
717
807
|
lines = ["📊 **Analytics de Documentação**"]
|
|
718
808
|
for key, value in analytics.items():
|
|
719
809
|
lines.append(f"- {key}: {value}")
|
|
@@ -725,7 +815,10 @@ class KnowledgeTool(Tool):
|
|
|
725
815
|
docs = await self._service.doc_children(payload.id)
|
|
726
816
|
if not docs:
|
|
727
817
|
return text("📄 Nenhum filho cadastrado para o documento informado.")
|
|
728
|
-
body = "\n".join(
|
|
818
|
+
body = "\n".join(
|
|
819
|
+
f"- {doc.get('title', 'Sem título')} (ID: {doc.get('id')})"
|
|
820
|
+
for doc in docs
|
|
821
|
+
)
|
|
729
822
|
return text(f"📄 **Filhos:**\n{body}")
|
|
730
823
|
|
|
731
824
|
if action is KnowledgeAction.DOC_TREE:
|
|
@@ -742,7 +835,9 @@ class KnowledgeTool(Tool):
|
|
|
742
835
|
if not payload.id:
|
|
743
836
|
return text("❌ Informe o ID da documentação.")
|
|
744
837
|
if payload.doc_parent_id is None and payload.doc_position is None:
|
|
745
|
-
return text(
|
|
838
|
+
return text(
|
|
839
|
+
"❌ Informe doc_parent_id, doc_position ou ambos para mover."
|
|
840
|
+
)
|
|
746
841
|
move_payload = {
|
|
747
842
|
"new_parent_id": payload.doc_parent_id,
|
|
748
843
|
"new_position": payload.doc_position,
|
|
@@ -760,7 +855,9 @@ class KnowledgeTool(Tool):
|
|
|
760
855
|
if not payload.id:
|
|
761
856
|
return text("❌ Informe o ID da documentação.")
|
|
762
857
|
if not payload.doc_version:
|
|
763
|
-
return text(
|
|
858
|
+
return text(
|
|
859
|
+
"❌ Informe doc_version com o número/identificador da versão."
|
|
860
|
+
)
|
|
764
861
|
version_payload = {
|
|
765
862
|
"title": payload.doc_title or f"Version {payload.doc_version}",
|
|
766
863
|
"version": payload.doc_version,
|
|
@@ -785,11 +882,18 @@ class KnowledgeTool(Tool):
|
|
|
785
882
|
|
|
786
883
|
return text(
|
|
787
884
|
"❌ Ação de documentação não suportada.\n\nEscolha um dos valores:\n"
|
|
788
|
-
+ "\n".join(
|
|
885
|
+
+ "\n".join(
|
|
886
|
+
f"- `{value}`"
|
|
887
|
+
for value in KnowledgeAction.choices()
|
|
888
|
+
if value.startswith("doc_")
|
|
889
|
+
)
|
|
789
890
|
)
|
|
790
891
|
|
|
791
892
|
async def _handle_help(self):
|
|
792
|
-
return text(
|
|
893
|
+
return text(
|
|
894
|
+
"📚 **Ações disponíveis para knowledge**\n\n"
|
|
895
|
+
+ KnowledgeAction.formatted_help()
|
|
896
|
+
)
|
|
793
897
|
|
|
794
898
|
|
|
795
899
|
def _format_work(item: Dict[str, Any], *, header: Optional[str] = None) -> str:
|
|
@@ -810,7 +914,9 @@ def _format_work(item: Dict[str, Any], *, header: Optional[str] = None) -> str:
|
|
|
810
914
|
]
|
|
811
915
|
)
|
|
812
916
|
if item.get("due_date") or item.get("dueDate"):
|
|
813
|
-
lines.append(
|
|
917
|
+
lines.append(
|
|
918
|
+
f"Vencimento: {_format_date(item.get('due_date') or item.get('dueDate'))}"
|
|
919
|
+
)
|
|
814
920
|
return "\n".join(lines)
|
|
815
921
|
|
|
816
922
|
|
|
@@ -844,9 +950,13 @@ def _format_sprint(sprint: Dict[str, Any], header: Optional[str] = None) -> str:
|
|
|
844
950
|
]
|
|
845
951
|
)
|
|
846
952
|
if sprint.get("start_date") or sprint.get("startDate"):
|
|
847
|
-
lines.append(
|
|
953
|
+
lines.append(
|
|
954
|
+
f"Início: {_format_date(sprint.get('start_date') or sprint.get('startDate'))}"
|
|
955
|
+
)
|
|
848
956
|
if sprint.get("end_date") or sprint.get("endDate"):
|
|
849
|
-
lines.append(
|
|
957
|
+
lines.append(
|
|
958
|
+
f"Fim: {_format_date(sprint.get('end_date') or sprint.get('endDate'))}"
|
|
959
|
+
)
|
|
850
960
|
return "\n".join(lines)
|
|
851
961
|
|
|
852
962
|
|
|
@@ -898,7 +1008,9 @@ def _format_doc(doc: Dict[str, Any], header: Optional[str] = None) -> str:
|
|
|
898
1008
|
]
|
|
899
1009
|
)
|
|
900
1010
|
if doc.get("updated_at") or doc.get("updatedAt"):
|
|
901
|
-
lines.append(
|
|
1011
|
+
lines.append(
|
|
1012
|
+
f"Atualizado em: {_format_date(doc.get('updated_at') or doc.get('updatedAt'))}"
|
|
1013
|
+
)
|
|
902
1014
|
return "\n".join(lines)
|
|
903
1015
|
|
|
904
1016
|
|