fenix-mcp 0.5.12__py3-none-any.whl → 0.7.0__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 +1 -1
- fenix_mcp/application/tool_base.py +114 -2
- fenix_mcp/application/tools/initialize.py +23 -2
- fenix_mcp/application/tools/intelligence.py +50 -52
- fenix_mcp/application/tools/knowledge.py +250 -90
- fenix_mcp/application/tools/productivity.py +26 -10
- fenix_mcp/application/tools/user_config.py +18 -5
- fenix_mcp/domain/intelligence.py +3 -30
- {fenix_mcp-0.5.12.dist-info → fenix_mcp-0.7.0.dist-info}/METADATA +1 -1
- {fenix_mcp-0.5.12.dist-info → fenix_mcp-0.7.0.dist-info}/RECORD +13 -13
- {fenix_mcp-0.5.12.dist-info → fenix_mcp-0.7.0.dist-info}/WHEEL +0 -0
- {fenix_mcp-0.5.12.dist-info → fenix_mcp-0.7.0.dist-info}/entry_points.txt +0 -0
- {fenix_mcp-0.5.12.dist-info → fenix_mcp-0.7.0.dist-info}/top_level.txt +0 -0
fenix_mcp/__init__.py
CHANGED
|
@@ -4,12 +4,124 @@
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
|
-
from typing import Any, Dict, Type
|
|
7
|
+
from typing import Annotated, Any, Dict, List, Optional, Type, TypeVar
|
|
8
8
|
|
|
9
|
-
from pydantic import BaseModel, ConfigDict
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field, StringConstraints
|
|
10
10
|
|
|
11
11
|
from fenix_mcp.infrastructure.context import AppContext
|
|
12
12
|
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def sanitize_null(value: Optional[T]) -> Optional[T]:
|
|
17
|
+
"""Convert string 'null' to None. Handles AI agents passing 'null' as string."""
|
|
18
|
+
if value == "null" or value == "None" or value == "":
|
|
19
|
+
return None
|
|
20
|
+
return value
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def sanitize_null_list(value: Optional[List[T]]) -> Optional[List[T]]:
|
|
24
|
+
"""Sanitize list values, converting 'null' string to None."""
|
|
25
|
+
if value == "null" or value == "None" or value == "": # type: ignore
|
|
26
|
+
return None
|
|
27
|
+
if isinstance(value, list):
|
|
28
|
+
return [v for v in value if v != "null" and v != "None" and v != ""]
|
|
29
|
+
return value
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# =============================================================================
|
|
33
|
+
# Type aliases for common field types - generates proper JSON schema
|
|
34
|
+
# =============================================================================
|
|
35
|
+
|
|
36
|
+
# UUID string - format: uuid, with regex pattern validation
|
|
37
|
+
UUIDStr = Annotated[
|
|
38
|
+
str,
|
|
39
|
+
Field(
|
|
40
|
+
pattern=r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
|
|
41
|
+
json_schema_extra={"format": "uuid"},
|
|
42
|
+
),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# ISO 8601 date string (YYYY-MM-DD)
|
|
46
|
+
DateStr = Annotated[
|
|
47
|
+
str,
|
|
48
|
+
Field(
|
|
49
|
+
pattern=r"^\d{4}-\d{2}-\d{2}$",
|
|
50
|
+
json_schema_extra={"format": "date"},
|
|
51
|
+
examples=["2025-01-15"],
|
|
52
|
+
),
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
# ISO 8601 datetime string (YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)
|
|
56
|
+
DateTimeStr = Annotated[
|
|
57
|
+
str,
|
|
58
|
+
Field(
|
|
59
|
+
pattern=r"^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$",
|
|
60
|
+
json_schema_extra={"format": "date-time"},
|
|
61
|
+
examples=["2025-01-15T10:30:00Z", "2025-01-15"],
|
|
62
|
+
),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
# Markdown content - indicates rich text
|
|
66
|
+
MarkdownStr = Annotated[
|
|
67
|
+
str,
|
|
68
|
+
Field(
|
|
69
|
+
json_schema_extra={"format": "markdown", "contentMediaType": "text/markdown"},
|
|
70
|
+
),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
# Short title/name strings (1-300 chars)
|
|
74
|
+
TitleStr = Annotated[
|
|
75
|
+
str,
|
|
76
|
+
StringConstraints(min_length=1, max_length=300),
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
# Description strings (up to 1000 chars)
|
|
80
|
+
DescriptionStr = Annotated[
|
|
81
|
+
str,
|
|
82
|
+
StringConstraints(max_length=1000),
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
# Tag string (lowercase, alphanumeric with hyphens/underscores)
|
|
86
|
+
TagStr = Annotated[
|
|
87
|
+
str,
|
|
88
|
+
Field(
|
|
89
|
+
pattern=r"^[a-zA-Z0-9_-]+$",
|
|
90
|
+
examples=["bug", "feature", "high-priority"],
|
|
91
|
+
),
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
# Category strings
|
|
95
|
+
CategoryStr = Annotated[
|
|
96
|
+
str,
|
|
97
|
+
StringConstraints(max_length=100),
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
# Language code (ISO 639-1)
|
|
101
|
+
LanguageStr = Annotated[
|
|
102
|
+
str,
|
|
103
|
+
Field(
|
|
104
|
+
pattern=r"^[a-z]{2}(-[A-Z]{2})?$",
|
|
105
|
+
examples=["pt", "en", "pt-BR", "en-US"],
|
|
106
|
+
),
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
# Version string (semver-like)
|
|
110
|
+
VersionStr = Annotated[
|
|
111
|
+
str,
|
|
112
|
+
Field(
|
|
113
|
+
pattern=r"^[0-9]+(\.[0-9]+)*(-[a-zA-Z0-9]+)?$",
|
|
114
|
+
max_length=20,
|
|
115
|
+
examples=["1.0", "2.1.3", "1.0.0-beta"],
|
|
116
|
+
),
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
# Emoji string (single emoji or short code)
|
|
120
|
+
EmojiStr = Annotated[
|
|
121
|
+
str,
|
|
122
|
+
StringConstraints(max_length=10),
|
|
123
|
+
]
|
|
124
|
+
|
|
13
125
|
|
|
14
126
|
class ToolRequest(BaseModel):
|
|
15
127
|
"""Base request payload."""
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
from enum import Enum
|
|
7
6
|
import json
|
|
7
|
+
from enum import Enum
|
|
8
8
|
from typing import List, Optional
|
|
9
9
|
|
|
10
10
|
from pydantic import Field
|
|
@@ -93,7 +93,28 @@ class InitializeTool(Tool):
|
|
|
93
93
|
if data.recent_memories:
|
|
94
94
|
payload_dict["recent_memories"] = data.recent_memories
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
# Extract key IDs for easy reference
|
|
97
|
+
profile = data.profile or {}
|
|
98
|
+
user_info = profile.get("user") or {}
|
|
99
|
+
tenant_info = profile.get("tenant") or {}
|
|
100
|
+
team_info = profile.get("team") or {}
|
|
101
|
+
|
|
102
|
+
context_lines = ["📋 **Contexto do Usuário**"]
|
|
103
|
+
if user_info.get("id"):
|
|
104
|
+
context_lines.append(f"- **user_id**: `{user_info['id']}`")
|
|
105
|
+
if user_info.get("name"):
|
|
106
|
+
context_lines.append(f"- **user_name**: {user_info['name']}")
|
|
107
|
+
if tenant_info.get("id"):
|
|
108
|
+
context_lines.append(f"- **tenant_id**: `{tenant_info['id']}`")
|
|
109
|
+
if tenant_info.get("name"):
|
|
110
|
+
context_lines.append(f"- **tenant_name**: {tenant_info['name']}")
|
|
111
|
+
if team_info.get("id"):
|
|
112
|
+
context_lines.append(f"- **team_id**: `{team_info['id']}`")
|
|
113
|
+
if team_info.get("name"):
|
|
114
|
+
context_lines.append(f"- **team_name**: {team_info['name']}")
|
|
115
|
+
|
|
116
|
+
message_lines = context_lines + [
|
|
117
|
+
"",
|
|
97
118
|
"📦 **Dados de inicialização completos**",
|
|
98
119
|
"```json",
|
|
99
120
|
json.dumps(payload_dict, ensure_ascii=False, indent=2),
|
|
@@ -9,7 +9,16 @@ from typing import Any, Dict, List, Optional
|
|
|
9
9
|
from pydantic import Field, field_validator
|
|
10
10
|
|
|
11
11
|
from fenix_mcp.application.presenters import text
|
|
12
|
-
from fenix_mcp.application.tool_base import
|
|
12
|
+
from fenix_mcp.application.tool_base import (
|
|
13
|
+
CategoryStr,
|
|
14
|
+
DateTimeStr,
|
|
15
|
+
MarkdownStr,
|
|
16
|
+
TagStr,
|
|
17
|
+
TitleStr,
|
|
18
|
+
Tool,
|
|
19
|
+
ToolRequest,
|
|
20
|
+
UUIDStr,
|
|
21
|
+
)
|
|
13
22
|
from fenix_mcp.domain.intelligence import IntelligenceService, build_metadata
|
|
14
23
|
from fenix_mcp.infrastructure.context import AppContext
|
|
15
24
|
|
|
@@ -31,9 +40,8 @@ class IntelligenceAction(str, Enum):
|
|
|
31
40
|
"memory_consolidate",
|
|
32
41
|
"Consolida múltiplas memórias em uma principal.",
|
|
33
42
|
)
|
|
34
|
-
PRIORITY = ("memory_priority", "Retorna memórias ordenadas por prioridade.")
|
|
35
|
-
ANALYTICS = ("memory_analytics", "Calcula métricas e analytics das memórias.")
|
|
36
43
|
UPDATE = ("memory_update", "Atualiza campos de uma memória existente.")
|
|
44
|
+
DELETE = ("memory_delete", "Remove uma memória pelo ID.")
|
|
37
45
|
HELP = ("memory_help", "Mostra as ações suportadas e seus usos.")
|
|
38
46
|
|
|
39
47
|
@classmethod
|
|
@@ -62,18 +70,19 @@ ACTION_FIELD_DESCRIPTION = (
|
|
|
62
70
|
|
|
63
71
|
class IntelligenceRequest(ToolRequest):
|
|
64
72
|
action: IntelligenceAction = Field(description=ACTION_FIELD_DESCRIPTION)
|
|
65
|
-
title: Optional[
|
|
66
|
-
content: Optional[
|
|
67
|
-
default=None, description="Conteúdo/texto da memória."
|
|
73
|
+
title: Optional[TitleStr] = Field(default=None, description="Título da memória.")
|
|
74
|
+
content: Optional[MarkdownStr] = Field(
|
|
75
|
+
default=None, description="Conteúdo/texto da memória (Markdown)."
|
|
68
76
|
)
|
|
69
|
-
metadata: Optional[
|
|
77
|
+
metadata: Optional[MarkdownStr] = Field(
|
|
70
78
|
default=None,
|
|
71
79
|
description="Metadata estruturada da memória (formato pipe, toml compacto, etc.).",
|
|
72
80
|
)
|
|
73
81
|
context: Optional[str] = Field(default=None, description="Contexto adicional.")
|
|
74
82
|
source: Optional[str] = Field(default=None, description="Fonte da memória.")
|
|
75
83
|
importance: Optional[str] = Field(
|
|
76
|
-
default=None,
|
|
84
|
+
default=None,
|
|
85
|
+
description="Nível de importância da memória (low, medium, high, critical).",
|
|
77
86
|
)
|
|
78
87
|
include_content: bool = Field(
|
|
79
88
|
default=False,
|
|
@@ -83,7 +92,7 @@ class IntelligenceRequest(ToolRequest):
|
|
|
83
92
|
default=False,
|
|
84
93
|
description="Retornar metadata completa das memórias? Defina true para incluir o campo bruto.",
|
|
85
94
|
)
|
|
86
|
-
tags: Optional[List[
|
|
95
|
+
tags: Optional[List[TagStr]] = Field(
|
|
87
96
|
default=None,
|
|
88
97
|
description="Tags da memória como array JSON de strings.",
|
|
89
98
|
json_schema_extra={"example": ["tag1", "tag2", "tag3"]},
|
|
@@ -120,34 +129,44 @@ class IntelligenceRequest(ToolRequest):
|
|
|
120
129
|
limit: int = Field(default=20, ge=1, le=100, description="Limite de resultados.")
|
|
121
130
|
offset: int = Field(default=0, ge=0, description="Offset para paginação.")
|
|
122
131
|
query: Optional[str] = Field(default=None, description="Termo de busca.")
|
|
123
|
-
category: Optional[
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
category: Optional[CategoryStr] = Field(
|
|
133
|
+
default=None, description="Categoria para filtro."
|
|
134
|
+
)
|
|
135
|
+
date_from: Optional[DateTimeStr] = Field(
|
|
136
|
+
default=None, description="Filtro inicial (ISO 8601)."
|
|
137
|
+
)
|
|
138
|
+
date_to: Optional[DateTimeStr] = Field(
|
|
139
|
+
default=None, description="Filtro final (ISO 8601)."
|
|
140
|
+
)
|
|
126
141
|
threshold: float = Field(
|
|
127
142
|
default=0.8, ge=0, le=1, description="Limite mínimo de similaridade."
|
|
128
143
|
)
|
|
129
144
|
max_results: int = Field(
|
|
130
145
|
default=5, ge=1, le=20, description="Máximo de memórias similares."
|
|
131
146
|
)
|
|
132
|
-
memory_ids: Optional[List[
|
|
133
|
-
default=None, description="IDs para consolidação."
|
|
147
|
+
memory_ids: Optional[List[UUIDStr]] = Field(
|
|
148
|
+
default=None, description="IDs das memórias para consolidação (UUIDs)."
|
|
134
149
|
)
|
|
135
150
|
strategy: str = Field(default="merge", description="Estratégia de consolidação.")
|
|
136
151
|
time_range: str = Field(
|
|
137
152
|
default="month", description="Janela de tempo para analytics."
|
|
138
153
|
)
|
|
139
154
|
group_by: str = Field(default="category", description="Agrupamento para analytics.")
|
|
140
|
-
id: Optional[
|
|
141
|
-
documentation_item_id: Optional[
|
|
142
|
-
default=None, description="ID de documentação relacionada."
|
|
155
|
+
id: Optional[UUIDStr] = Field(default=None, description="ID da memória (UUID).")
|
|
156
|
+
documentation_item_id: Optional[UUIDStr] = Field(
|
|
157
|
+
default=None, description="ID de documentação relacionada (UUID)."
|
|
143
158
|
)
|
|
144
|
-
mode_id: Optional[
|
|
145
|
-
|
|
146
|
-
work_item_id: Optional[str] = Field(
|
|
147
|
-
default=None, description="ID do work item relacionado."
|
|
159
|
+
mode_id: Optional[UUIDStr] = Field(
|
|
160
|
+
default=None, description="ID do modo relacionado (UUID)."
|
|
148
161
|
)
|
|
149
|
-
|
|
150
|
-
default=None, description="ID
|
|
162
|
+
rule_id: Optional[UUIDStr] = Field(
|
|
163
|
+
default=None, description="ID da regra relacionada (UUID)."
|
|
164
|
+
)
|
|
165
|
+
work_item_id: Optional[UUIDStr] = Field(
|
|
166
|
+
default=None, description="ID do work item relacionado (UUID)."
|
|
167
|
+
)
|
|
168
|
+
sprint_id: Optional[UUIDStr] = Field(
|
|
169
|
+
default=None, description="ID do sprint relacionado (UUID)."
|
|
151
170
|
)
|
|
152
171
|
|
|
153
172
|
|
|
@@ -174,12 +193,10 @@ class IntelligenceTool(Tool):
|
|
|
174
193
|
return await self._handle_similarity(payload)
|
|
175
194
|
if action is IntelligenceAction.CONSOLIDATE:
|
|
176
195
|
return await self._handle_consolidate(payload)
|
|
177
|
-
if action is IntelligenceAction.PRIORITY:
|
|
178
|
-
return await self._handle_priority(payload)
|
|
179
|
-
if action is IntelligenceAction.ANALYTICS:
|
|
180
|
-
return await self._handle_analytics(payload)
|
|
181
196
|
if action is IntelligenceAction.UPDATE:
|
|
182
197
|
return await self._handle_update(payload)
|
|
198
|
+
if action is IntelligenceAction.DELETE:
|
|
199
|
+
return await self._handle_delete(payload)
|
|
183
200
|
return text(
|
|
184
201
|
"❌ Ação inválida para intelligence.\n\nEscolha um dos valores:\n"
|
|
185
202
|
+ "\n".join(f"- `{value}`" for value in IntelligenceAction.choices())
|
|
@@ -276,31 +293,6 @@ class IntelligenceTool(Tool):
|
|
|
276
293
|
]
|
|
277
294
|
return text("\n".join(lines))
|
|
278
295
|
|
|
279
|
-
async def _handle_priority(self, payload: IntelligenceRequest):
|
|
280
|
-
memories = await self._service.priority_memories(limit=payload.limit)
|
|
281
|
-
if not memories:
|
|
282
|
-
return text("✅ Nenhuma memória prioritária no momento.")
|
|
283
|
-
body = "\n\n".join(_format_memory(mem) for mem in memories)
|
|
284
|
-
return text(f"🧠 **Memórias prioritárias ({len(memories)}):**\n\n{body}")
|
|
285
|
-
|
|
286
|
-
async def _handle_analytics(self, payload: IntelligenceRequest):
|
|
287
|
-
analytics = await self._service.analytics(
|
|
288
|
-
time_range=payload.time_range,
|
|
289
|
-
group_by=payload.group_by,
|
|
290
|
-
)
|
|
291
|
-
lines = [
|
|
292
|
-
"📊 **Memória - Analytics**",
|
|
293
|
-
f"Total: {analytics.get('total_memories', 0)}",
|
|
294
|
-
f"Novas: {analytics.get('new_memories', 0)}",
|
|
295
|
-
f"Mais acessada: {analytics.get('most_accessed', 'N/A')}",
|
|
296
|
-
f"Acesso médio: {analytics.get('avg_access_count', 'N/A')}",
|
|
297
|
-
]
|
|
298
|
-
by_group = analytics.get("by_group")
|
|
299
|
-
if isinstance(by_group, dict):
|
|
300
|
-
lines.append("\nPor grupo:")
|
|
301
|
-
lines.extend(f"- {key}: {value}" for key, value in by_group.items())
|
|
302
|
-
return text("\n".join(lines))
|
|
303
|
-
|
|
304
296
|
async def _handle_update(self, payload: IntelligenceRequest):
|
|
305
297
|
if not payload.id:
|
|
306
298
|
return text("❌ Informe o ID da memória para atualização.")
|
|
@@ -342,6 +334,12 @@ class IntelligenceTool(Tool):
|
|
|
342
334
|
)
|
|
343
335
|
)
|
|
344
336
|
|
|
337
|
+
async def _handle_delete(self, payload: IntelligenceRequest):
|
|
338
|
+
if not payload.id:
|
|
339
|
+
return text("❌ Informe o ID da memória para remover.")
|
|
340
|
+
await self._service.delete_memory(payload.id)
|
|
341
|
+
return text(f"🗑️ Memória {payload.id} removida com sucesso.")
|
|
342
|
+
|
|
345
343
|
async def _handle_help(self):
|
|
346
344
|
return text(
|
|
347
345
|
"📚 **Ações disponíveis para intelligence**\n\n"
|
|
@@ -9,7 +9,22 @@ from typing import Any, Dict, List, Optional
|
|
|
9
9
|
from pydantic import Field
|
|
10
10
|
|
|
11
11
|
from fenix_mcp.application.presenters import text
|
|
12
|
-
from fenix_mcp.application.tool_base import
|
|
12
|
+
from fenix_mcp.application.tool_base import (
|
|
13
|
+
CategoryStr,
|
|
14
|
+
DateTimeStr,
|
|
15
|
+
DescriptionStr,
|
|
16
|
+
EmojiStr,
|
|
17
|
+
LanguageStr,
|
|
18
|
+
MarkdownStr,
|
|
19
|
+
TagStr,
|
|
20
|
+
TitleStr,
|
|
21
|
+
Tool,
|
|
22
|
+
ToolRequest,
|
|
23
|
+
UUIDStr,
|
|
24
|
+
VersionStr,
|
|
25
|
+
sanitize_null,
|
|
26
|
+
sanitize_null_list,
|
|
27
|
+
)
|
|
13
28
|
from fenix_mcp.domain.knowledge import KnowledgeService, _format_date
|
|
14
29
|
from fenix_mcp.infrastructure.context import AppContext
|
|
15
30
|
|
|
@@ -41,6 +56,16 @@ class KnowledgeAction(str, Enum):
|
|
|
41
56
|
WORK_ANALYTICS = ("work_analytics", "Retorna métricas consolidadas de work items.")
|
|
42
57
|
WORK_BY_BOARD = ("work_by_board", "Lista work items associados a um board.")
|
|
43
58
|
WORK_BY_SPRINT = ("work_by_sprint", "Lista work items associados a um sprint.")
|
|
59
|
+
WORK_BY_EPIC = ("work_by_epic", "Lista work items associados a um épico.")
|
|
60
|
+
WORK_CHILDREN = ("work_children", "Lista work items filhos de um item pai.")
|
|
61
|
+
WORK_STATUS_UPDATE = (
|
|
62
|
+
"work_status_update",
|
|
63
|
+
"Atualiza apenas o status de um work item.",
|
|
64
|
+
)
|
|
65
|
+
WORK_ASSIGN_SPRINT = (
|
|
66
|
+
"work_assign_sprint",
|
|
67
|
+
"Atribui work items a um sprint.",
|
|
68
|
+
)
|
|
44
69
|
|
|
45
70
|
# Boards
|
|
46
71
|
BOARD_LIST = ("board_list", "Lista boards disponíveis com filtros opcionais.")
|
|
@@ -130,19 +155,25 @@ _ALLOWED_DOC_TYPES = {
|
|
|
130
155
|
|
|
131
156
|
class KnowledgeRequest(ToolRequest):
|
|
132
157
|
action: KnowledgeAction = Field(description=ACTION_FIELD_DESCRIPTION)
|
|
133
|
-
id: Optional[
|
|
158
|
+
id: Optional[UUIDStr] = Field(
|
|
159
|
+
default=None, description="ID principal do recurso (UUID)."
|
|
160
|
+
)
|
|
134
161
|
limit: int = Field(default=20, ge=1, le=100, description="Limite de resultados.")
|
|
135
162
|
offset: int = Field(
|
|
136
163
|
default=0, ge=0, description="Offset de paginação (quando suportado)."
|
|
137
164
|
)
|
|
138
|
-
team_id: Optional[
|
|
139
|
-
default=None, description="ID do time para filtros (
|
|
165
|
+
team_id: Optional[UUIDStr] = Field(
|
|
166
|
+
default=None, description="ID do time para filtros (UUID)."
|
|
140
167
|
)
|
|
141
|
-
board_id: Optional[
|
|
142
|
-
|
|
143
|
-
|
|
168
|
+
board_id: Optional[UUIDStr] = Field(
|
|
169
|
+
default=None, description="ID do board associado (UUID)."
|
|
170
|
+
)
|
|
171
|
+
sprint_id: Optional[UUIDStr] = Field(
|
|
172
|
+
default=None, description="ID do sprint associado (UUID)."
|
|
173
|
+
)
|
|
174
|
+
epic_id: Optional[UUIDStr] = Field(
|
|
175
|
+
default=None, description="ID do épico associado (UUID)."
|
|
144
176
|
)
|
|
145
|
-
epic_id: Optional[str] = Field(default=None, description="ID do épico associado.")
|
|
146
177
|
query: Optional[str] = Field(default=None, description="Filtro/busca.")
|
|
147
178
|
return_content: Optional[bool] = Field(
|
|
148
179
|
default=None, description="Retorna conteúdo completo."
|
|
@@ -155,71 +186,130 @@ class KnowledgeRequest(ToolRequest):
|
|
|
155
186
|
)
|
|
156
187
|
|
|
157
188
|
# Work item fields
|
|
158
|
-
work_title: Optional[
|
|
159
|
-
|
|
160
|
-
|
|
189
|
+
work_title: Optional[TitleStr] = Field(
|
|
190
|
+
default=None, description="Título do work item."
|
|
191
|
+
)
|
|
192
|
+
work_description: Optional[MarkdownStr] = Field(
|
|
193
|
+
default=None, description="Descrição do work item (Markdown)."
|
|
194
|
+
)
|
|
195
|
+
work_type: Optional[str] = Field(
|
|
196
|
+
default="task",
|
|
197
|
+
description="Tipo do work item (epic, feature, story, task, subtask, bug).",
|
|
198
|
+
)
|
|
199
|
+
work_status: Optional[str] = Field(
|
|
200
|
+
default=None,
|
|
201
|
+
description="Status do work item (backlog, todo, in_progress, review, testing, done, cancelled).",
|
|
161
202
|
)
|
|
162
|
-
work_type: Optional[str] = Field(default="task", description="Tipo do work item.")
|
|
163
203
|
work_priority: Optional[str] = Field(
|
|
164
|
-
default=None,
|
|
204
|
+
default=None,
|
|
205
|
+
description="Prioridade do work item (critical, high, medium, low).",
|
|
206
|
+
)
|
|
207
|
+
story_points: Optional[int] = Field(
|
|
208
|
+
default=None, ge=0, le=100, description="Story points (0-100)."
|
|
209
|
+
)
|
|
210
|
+
assignee_id: Optional[UUIDStr] = Field(
|
|
211
|
+
default=None, description="ID do responsável (UUID)."
|
|
212
|
+
)
|
|
213
|
+
parent_id: Optional[UUIDStr] = Field(
|
|
214
|
+
default=None, description="ID do item pai (UUID)."
|
|
215
|
+
)
|
|
216
|
+
work_due_date: Optional[DateTimeStr] = Field(
|
|
217
|
+
default=None, description="Data de vencimento do work item (ISO 8601)."
|
|
218
|
+
)
|
|
219
|
+
work_tags: Optional[List[TagStr]] = Field(
|
|
220
|
+
default=None, description="Tags do work item."
|
|
221
|
+
)
|
|
222
|
+
work_item_ids: Optional[List[UUIDStr]] = Field(
|
|
223
|
+
default=None,
|
|
224
|
+
description="Lista de IDs de work items para operações em lote (UUIDs).",
|
|
165
225
|
)
|
|
166
|
-
story_points: Optional[int] = Field(default=None, description="Story points.")
|
|
167
|
-
assignee_id: Optional[str] = Field(default=None, description="ID do responsável.")
|
|
168
|
-
parent_id: Optional[str] = Field(default=None, description="ID do item pai.")
|
|
169
226
|
|
|
170
227
|
# Mode fields
|
|
171
|
-
mode_id: Optional[
|
|
172
|
-
|
|
173
|
-
|
|
228
|
+
mode_id: Optional[UUIDStr] = Field(
|
|
229
|
+
default=None, description="ID do modo relacionado (UUID)."
|
|
230
|
+
)
|
|
231
|
+
mode_name: Optional[TitleStr] = Field(default=None, description="Nome do modo.")
|
|
232
|
+
mode_description: Optional[DescriptionStr] = Field(
|
|
174
233
|
default=None, description="Descrição do modo."
|
|
175
234
|
)
|
|
176
|
-
mode_content: Optional[
|
|
235
|
+
mode_content: Optional[MarkdownStr] = Field(
|
|
236
|
+
default=None, description="Conteúdo do modo (Markdown)."
|
|
237
|
+
)
|
|
177
238
|
mode_is_default: Optional[bool] = Field(
|
|
178
239
|
default=None, description="Indica se o modo é padrão."
|
|
179
240
|
)
|
|
241
|
+
mode_metadata: Optional[MarkdownStr] = Field(
|
|
242
|
+
default=None, description="Metadata do modo para processamento de IA."
|
|
243
|
+
)
|
|
180
244
|
|
|
181
245
|
# Rule fields
|
|
182
|
-
rule_id: Optional[
|
|
183
|
-
|
|
184
|
-
|
|
246
|
+
rule_id: Optional[UUIDStr] = Field(
|
|
247
|
+
default=None, description="ID da regra relacionada (UUID)."
|
|
248
|
+
)
|
|
249
|
+
rule_name: Optional[TitleStr] = Field(default=None, description="Nome da regra.")
|
|
250
|
+
rule_description: Optional[DescriptionStr] = Field(
|
|
185
251
|
default=None, description="Descrição da regra."
|
|
186
252
|
)
|
|
187
|
-
rule_content: Optional[
|
|
253
|
+
rule_content: Optional[MarkdownStr] = Field(
|
|
254
|
+
default=None, description="Conteúdo da regra (Markdown)."
|
|
255
|
+
)
|
|
188
256
|
rule_is_default: Optional[bool] = Field(default=None, description="Regra padrão.")
|
|
257
|
+
rule_metadata: Optional[MarkdownStr] = Field(
|
|
258
|
+
default=None, description="Metadata da regra para processamento de IA."
|
|
259
|
+
)
|
|
189
260
|
|
|
190
261
|
# Documentation fields
|
|
191
|
-
doc_title: Optional[
|
|
262
|
+
doc_title: Optional[TitleStr] = Field(
|
|
192
263
|
default=None, description="Título da documentação."
|
|
193
264
|
)
|
|
194
|
-
doc_description: Optional[
|
|
195
|
-
|
|
196
|
-
|
|
265
|
+
doc_description: Optional[DescriptionStr] = Field(
|
|
266
|
+
default=None, description="Descrição da documentação."
|
|
267
|
+
)
|
|
268
|
+
doc_content: Optional[MarkdownStr] = Field(
|
|
269
|
+
default=None, description="Conteúdo da documentação (Markdown)."
|
|
197
270
|
)
|
|
198
271
|
doc_status: Optional[str] = Field(
|
|
199
|
-
default=None,
|
|
272
|
+
default=None,
|
|
273
|
+
description="Status da documentação (draft, review, published, archived).",
|
|
274
|
+
)
|
|
275
|
+
doc_type: Optional[str] = Field(
|
|
276
|
+
default=None, description="Tipo da documentação (folder, page, api_doc, guide)."
|
|
277
|
+
)
|
|
278
|
+
doc_language: Optional[LanguageStr] = Field(
|
|
279
|
+
default=None, description="Idioma da documentação (ex: pt, en, pt-BR)."
|
|
280
|
+
)
|
|
281
|
+
doc_parent_id: Optional[UUIDStr] = Field(
|
|
282
|
+
default=None, description="ID do documento pai (UUID)."
|
|
200
283
|
)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
default=None, description="Idioma da documentação."
|
|
284
|
+
doc_team_id: Optional[UUIDStr] = Field(
|
|
285
|
+
default=None, description="ID do time responsável pela documentação (UUID)."
|
|
204
286
|
)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
default=None, description="Time responsável pela documentação."
|
|
287
|
+
doc_owner_id: Optional[UUIDStr] = Field(
|
|
288
|
+
default=None, description="ID do dono (UUID)."
|
|
208
289
|
)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
290
|
+
doc_reviewer_id: Optional[UUIDStr] = Field(
|
|
291
|
+
default=None, description="ID do revisor (UUID)."
|
|
292
|
+
)
|
|
293
|
+
doc_version: Optional[VersionStr] = Field(
|
|
294
|
+
default=None, description="Versão do documento (ex: 1.0, 2.1.3)."
|
|
295
|
+
)
|
|
296
|
+
doc_category: Optional[CategoryStr] = Field(default=None, description="Categoria.")
|
|
297
|
+
doc_tags: Optional[List[TagStr]] = Field(default=None, description="Tags.")
|
|
214
298
|
doc_position: Optional[int] = Field(
|
|
215
|
-
default=None, description="Posição desejada ao mover documentos."
|
|
299
|
+
default=None, ge=0, description="Posição desejada ao mover documentos."
|
|
216
300
|
)
|
|
217
|
-
doc_emoji: Optional[
|
|
301
|
+
doc_emoji: Optional[EmojiStr] = Field(
|
|
218
302
|
default=None, description="Emoji exibido junto ao documento."
|
|
219
303
|
)
|
|
220
|
-
doc_emote: Optional[
|
|
304
|
+
doc_emote: Optional[EmojiStr] = Field(
|
|
221
305
|
default=None, description="Alias para emoji, mantido por compatibilidade."
|
|
222
306
|
)
|
|
307
|
+
doc_keywords: Optional[List[TagStr]] = Field(
|
|
308
|
+
default=None, description="Palavras-chave para busca."
|
|
309
|
+
)
|
|
310
|
+
doc_is_public: Optional[bool] = Field(
|
|
311
|
+
default=None, description="Se o documento é público."
|
|
312
|
+
)
|
|
223
313
|
|
|
224
314
|
|
|
225
315
|
class KnowledgeTool(Tool):
|
|
@@ -260,17 +350,23 @@ class KnowledgeTool(Tool):
|
|
|
260
350
|
if action is KnowledgeAction.WORK_CREATE:
|
|
261
351
|
if not payload.work_title:
|
|
262
352
|
return text("❌ Informe work_title para criar o item.")
|
|
353
|
+
if not payload.team_id:
|
|
354
|
+
return text("❌ Informe team_id para criar o item.")
|
|
263
355
|
work = await self._service.work_create(
|
|
264
356
|
{
|
|
265
357
|
"title": payload.work_title,
|
|
266
358
|
"description": payload.work_description,
|
|
267
359
|
"item_type": payload.work_type,
|
|
360
|
+
"status": payload.work_status,
|
|
268
361
|
"priority": payload.work_priority,
|
|
269
362
|
"story_points": payload.story_points,
|
|
270
363
|
"assignee_id": payload.assignee_id,
|
|
271
364
|
"sprint_id": payload.sprint_id,
|
|
272
365
|
"board_id": payload.board_id,
|
|
273
366
|
"parent_id": payload.parent_id,
|
|
367
|
+
"team_id": payload.team_id,
|
|
368
|
+
"due_date": payload.work_due_date,
|
|
369
|
+
"tags": payload.work_tags,
|
|
274
370
|
}
|
|
275
371
|
)
|
|
276
372
|
return text(_format_work(work, header="✅ Work item criado"))
|
|
@@ -305,12 +401,16 @@ class KnowledgeTool(Tool):
|
|
|
305
401
|
"title": payload.work_title,
|
|
306
402
|
"description": payload.work_description,
|
|
307
403
|
"item_type": payload.work_type,
|
|
404
|
+
"status": payload.work_status,
|
|
308
405
|
"priority": payload.work_priority,
|
|
309
406
|
"story_points": payload.story_points,
|
|
310
407
|
"assignee_id": payload.assignee_id,
|
|
311
408
|
"sprint_id": payload.sprint_id,
|
|
312
409
|
"board_id": payload.board_id,
|
|
313
410
|
"parent_id": payload.parent_id,
|
|
411
|
+
"team_id": payload.team_id,
|
|
412
|
+
"due_date": payload.work_due_date,
|
|
413
|
+
"tags": payload.work_tags,
|
|
314
414
|
},
|
|
315
415
|
)
|
|
316
416
|
return text(_format_work(work, header="✅ Work item atualizado"))
|
|
@@ -322,20 +422,23 @@ class KnowledgeTool(Tool):
|
|
|
322
422
|
return text(f"🗑️ Work item {payload.id} removido.")
|
|
323
423
|
|
|
324
424
|
if action is KnowledgeAction.WORK_BACKLOG:
|
|
325
|
-
|
|
425
|
+
team_id = sanitize_null(payload.team_id)
|
|
426
|
+
if not team_id:
|
|
326
427
|
return text("❌ Informe team_id para consultar o backlog.")
|
|
327
|
-
items = await self._service.work_backlog(team_id=
|
|
428
|
+
items = await self._service.work_backlog(team_id=team_id)
|
|
328
429
|
if not items:
|
|
329
430
|
return text("📋 Backlog vazio para o time informado.")
|
|
330
431
|
body = "\n\n".join(_format_work(item) for item in items)
|
|
331
432
|
return text(f"📋 **Backlog ({len(items)}):**\n\n{body}")
|
|
332
433
|
|
|
333
434
|
if action is KnowledgeAction.WORK_SEARCH:
|
|
334
|
-
|
|
435
|
+
query = sanitize_null(payload.query)
|
|
436
|
+
team_id = sanitize_null(payload.team_id)
|
|
437
|
+
if not query or not team_id:
|
|
335
438
|
return text("❌ Informe query e team_id para buscar work items.")
|
|
336
439
|
items = await self._service.work_search(
|
|
337
|
-
query=
|
|
338
|
-
team_id=
|
|
440
|
+
query=query,
|
|
441
|
+
team_id=team_id,
|
|
339
442
|
limit=payload.limit,
|
|
340
443
|
)
|
|
341
444
|
if not items:
|
|
@@ -344,9 +447,10 @@ class KnowledgeTool(Tool):
|
|
|
344
447
|
return text(f"🔍 **Resultados ({len(items)}):**\n\n{body}")
|
|
345
448
|
|
|
346
449
|
if action is KnowledgeAction.WORK_ANALYTICS:
|
|
347
|
-
|
|
450
|
+
team_id = sanitize_null(payload.team_id)
|
|
451
|
+
if not team_id:
|
|
348
452
|
return text("❌ Informe team_id para obter analytics.")
|
|
349
|
-
analytics = await self._service.work_analytics(team_id=
|
|
453
|
+
analytics = await self._service.work_analytics(team_id=team_id)
|
|
350
454
|
lines = ["📊 **Analytics de Work Items**"]
|
|
351
455
|
for key, value in analytics.items():
|
|
352
456
|
lines.append(f"- {key}: {value}")
|
|
@@ -370,6 +474,49 @@ class KnowledgeTool(Tool):
|
|
|
370
474
|
body = "\n\n".join(_format_work(item) for item in items)
|
|
371
475
|
return text(f"🏃 **Work items do sprint ({len(items)}):**\n\n{body}")
|
|
372
476
|
|
|
477
|
+
if action is KnowledgeAction.WORK_BY_EPIC:
|
|
478
|
+
if not payload.epic_id:
|
|
479
|
+
return text("❌ Informe epic_id para listar os itens.")
|
|
480
|
+
items = await self._service.work_by_epic(epic_id=payload.epic_id)
|
|
481
|
+
if not items:
|
|
482
|
+
return text("📦 Nenhum item vinculado ao épico informado.")
|
|
483
|
+
body = "\n\n".join(_format_work(item) for item in items)
|
|
484
|
+
return text(f"📦 **Work items do épico ({len(items)}):**\n\n{body}")
|
|
485
|
+
|
|
486
|
+
if action is KnowledgeAction.WORK_CHILDREN:
|
|
487
|
+
if not payload.id:
|
|
488
|
+
return text("❌ Informe o ID do work item pai.")
|
|
489
|
+
items = await self._service.work_children(payload.id)
|
|
490
|
+
if not items:
|
|
491
|
+
return text("👶 Nenhum item filho encontrado.")
|
|
492
|
+
body = "\n\n".join(_format_work(item) for item in items)
|
|
493
|
+
return text(f"👶 **Work items filhos ({len(items)}):**\n\n{body}")
|
|
494
|
+
|
|
495
|
+
if action is KnowledgeAction.WORK_STATUS_UPDATE:
|
|
496
|
+
if not payload.id:
|
|
497
|
+
return text("❌ Informe o ID do work item.")
|
|
498
|
+
if not payload.work_status:
|
|
499
|
+
return text("❌ Informe work_status para atualizar.")
|
|
500
|
+
work = await self._service.work_update_status(
|
|
501
|
+
payload.id,
|
|
502
|
+
{"status": payload.work_status},
|
|
503
|
+
)
|
|
504
|
+
return text(_format_work(work, header="✅ Status atualizado"))
|
|
505
|
+
|
|
506
|
+
if action is KnowledgeAction.WORK_ASSIGN_SPRINT:
|
|
507
|
+
if not payload.sprint_id:
|
|
508
|
+
return text("❌ Informe sprint_id para atribuir os itens.")
|
|
509
|
+
if not payload.work_item_ids:
|
|
510
|
+
return text("❌ Informe work_item_ids com a lista de IDs.")
|
|
511
|
+
await self._service.work_assign_to_sprint(
|
|
512
|
+
{
|
|
513
|
+
"sprint_id": payload.sprint_id,
|
|
514
|
+
"work_item_ids": payload.work_item_ids,
|
|
515
|
+
}
|
|
516
|
+
)
|
|
517
|
+
count = len(payload.work_item_ids)
|
|
518
|
+
return text(f"✅ {count} work item(s) atribuído(s) ao sprint.")
|
|
519
|
+
|
|
373
520
|
return text(
|
|
374
521
|
"❌ Ação de work item não suportada.\n\nEscolha um dos valores:\n"
|
|
375
522
|
+ "\n".join(
|
|
@@ -505,6 +652,7 @@ class KnowledgeTool(Tool):
|
|
|
505
652
|
"description": payload.mode_description,
|
|
506
653
|
"content": payload.mode_content,
|
|
507
654
|
"is_default": payload.mode_is_default,
|
|
655
|
+
"metadata": payload.mode_metadata,
|
|
508
656
|
}
|
|
509
657
|
)
|
|
510
658
|
return text(_format_mode(mode, header="✅ Modo criado"))
|
|
@@ -540,6 +688,7 @@ class KnowledgeTool(Tool):
|
|
|
540
688
|
"description": payload.mode_description,
|
|
541
689
|
"content": payload.mode_content,
|
|
542
690
|
"is_default": payload.mode_is_default,
|
|
691
|
+
"metadata": payload.mode_metadata,
|
|
543
692
|
},
|
|
544
693
|
)
|
|
545
694
|
return text(_format_mode(mode, header="✅ Modo atualizado"))
|
|
@@ -610,6 +759,7 @@ class KnowledgeTool(Tool):
|
|
|
610
759
|
"description": payload.rule_description,
|
|
611
760
|
"content": payload.rule_content,
|
|
612
761
|
"is_default": payload.rule_is_default,
|
|
762
|
+
"metadata": payload.rule_metadata,
|
|
613
763
|
}
|
|
614
764
|
)
|
|
615
765
|
return text(_format_rule(rule, header="✅ Regra criada"))
|
|
@@ -646,6 +796,7 @@ class KnowledgeTool(Tool):
|
|
|
646
796
|
"description": payload.rule_description,
|
|
647
797
|
"content": payload.rule_content,
|
|
648
798
|
"is_default": payload.rule_is_default,
|
|
799
|
+
"metadata": payload.rule_metadata,
|
|
649
800
|
},
|
|
650
801
|
)
|
|
651
802
|
return text(_format_rule(rule, header="✅ Regra atualizada"))
|
|
@@ -681,19 +832,21 @@ class KnowledgeTool(Tool):
|
|
|
681
832
|
doc = await self._service.doc_create(
|
|
682
833
|
{
|
|
683
834
|
"title": payload.doc_title,
|
|
684
|
-
"description": payload.doc_description,
|
|
685
|
-
"content": payload.doc_content,
|
|
686
|
-
"status": payload.doc_status,
|
|
687
|
-
"doc_type": payload.doc_type,
|
|
688
|
-
"language": payload.doc_language,
|
|
689
|
-
"parent_id": payload.doc_parent_id,
|
|
690
|
-
"team_id": payload.doc_team_id or payload.team_id,
|
|
691
|
-
"
|
|
692
|
-
"
|
|
693
|
-
"version": payload.doc_version,
|
|
694
|
-
"category": payload.doc_category,
|
|
695
|
-
"tags": payload.doc_tags,
|
|
696
|
-
"emoji": payload.doc_emoji or payload.doc_emote,
|
|
835
|
+
"description": sanitize_null(payload.doc_description),
|
|
836
|
+
"content": sanitize_null(payload.doc_content),
|
|
837
|
+
"status": sanitize_null(payload.doc_status),
|
|
838
|
+
"doc_type": sanitize_null(payload.doc_type),
|
|
839
|
+
"language": sanitize_null(payload.doc_language),
|
|
840
|
+
"parent_id": sanitize_null(payload.doc_parent_id),
|
|
841
|
+
"team_id": sanitize_null(payload.doc_team_id or payload.team_id),
|
|
842
|
+
"owner_user_id": sanitize_null(payload.doc_owner_id),
|
|
843
|
+
"reviewer_user_id": sanitize_null(payload.doc_reviewer_id),
|
|
844
|
+
"version": sanitize_null(payload.doc_version),
|
|
845
|
+
"category": sanitize_null(payload.doc_category),
|
|
846
|
+
"tags": sanitize_null_list(payload.doc_tags),
|
|
847
|
+
"emoji": sanitize_null(payload.doc_emoji or payload.doc_emote),
|
|
848
|
+
"keywords": sanitize_null_list(payload.doc_keywords),
|
|
849
|
+
"is_public": payload.doc_is_public,
|
|
697
850
|
}
|
|
698
851
|
)
|
|
699
852
|
return text(_format_doc(doc, header="✅ Documentação criada"))
|
|
@@ -729,20 +882,22 @@ class KnowledgeTool(Tool):
|
|
|
729
882
|
doc = await self._service.doc_update(
|
|
730
883
|
payload.id,
|
|
731
884
|
{
|
|
732
|
-
"title": payload.doc_title,
|
|
733
|
-
"description": payload.doc_description,
|
|
734
|
-
"content": payload.doc_content,
|
|
735
|
-
"status": payload.doc_status,
|
|
736
|
-
"doc_type": payload.doc_type,
|
|
737
|
-
"language": payload.doc_language,
|
|
738
|
-
"parent_id": payload.doc_parent_id,
|
|
739
|
-
"team_id": payload.doc_team_id or payload.team_id,
|
|
740
|
-
"
|
|
741
|
-
"
|
|
742
|
-
"version": payload.doc_version,
|
|
743
|
-
"category": payload.doc_category,
|
|
744
|
-
"tags": payload.doc_tags,
|
|
745
|
-
"emoji": payload.doc_emoji or payload.doc_emote,
|
|
885
|
+
"title": sanitize_null(payload.doc_title),
|
|
886
|
+
"description": sanitize_null(payload.doc_description),
|
|
887
|
+
"content": sanitize_null(payload.doc_content),
|
|
888
|
+
"status": sanitize_null(payload.doc_status),
|
|
889
|
+
"doc_type": sanitize_null(payload.doc_type),
|
|
890
|
+
"language": sanitize_null(payload.doc_language),
|
|
891
|
+
"parent_id": sanitize_null(payload.doc_parent_id),
|
|
892
|
+
"team_id": sanitize_null(payload.doc_team_id or payload.team_id),
|
|
893
|
+
"owner_user_id": sanitize_null(payload.doc_owner_id),
|
|
894
|
+
"reviewer_user_id": sanitize_null(payload.doc_reviewer_id),
|
|
895
|
+
"version": sanitize_null(payload.doc_version),
|
|
896
|
+
"category": sanitize_null(payload.doc_category),
|
|
897
|
+
"tags": sanitize_null_list(payload.doc_tags),
|
|
898
|
+
"emoji": sanitize_null(payload.doc_emoji or payload.doc_emote),
|
|
899
|
+
"keywords": sanitize_null_list(payload.doc_keywords),
|
|
900
|
+
"is_public": payload.doc_is_public,
|
|
746
901
|
},
|
|
747
902
|
)
|
|
748
903
|
return text(_format_doc(doc, header="✅ Documentação atualizada"))
|
|
@@ -754,11 +909,13 @@ class KnowledgeTool(Tool):
|
|
|
754
909
|
return text(f"🗑️ Documentação {payload.id} removida.")
|
|
755
910
|
|
|
756
911
|
if action is KnowledgeAction.DOC_SEARCH:
|
|
757
|
-
|
|
912
|
+
query = sanitize_null(payload.query)
|
|
913
|
+
team_id = sanitize_null(payload.doc_team_id or payload.team_id)
|
|
914
|
+
if not query or not team_id:
|
|
758
915
|
return text("❌ Informe query e team_id para buscar documentação.")
|
|
759
916
|
docs = await self._service.doc_search(
|
|
760
|
-
query=
|
|
761
|
-
team_id=
|
|
917
|
+
query=query,
|
|
918
|
+
team_id=team_id,
|
|
762
919
|
limit=payload.limit,
|
|
763
920
|
)
|
|
764
921
|
if not docs:
|
|
@@ -769,11 +926,10 @@ class KnowledgeTool(Tool):
|
|
|
769
926
|
return text(f"🔍 **Resultados ({len(docs)}):**\n\n{body}")
|
|
770
927
|
|
|
771
928
|
if action is KnowledgeAction.DOC_ROOTS:
|
|
772
|
-
|
|
929
|
+
team_id = sanitize_null(payload.doc_team_id or payload.team_id)
|
|
930
|
+
if not team_id:
|
|
773
931
|
return text("❌ Informe team_id para listar raízes.")
|
|
774
|
-
docs = await self._service.doc_roots(
|
|
775
|
-
team_id=payload.doc_team_id or payload.team_id
|
|
776
|
-
)
|
|
932
|
+
docs = await self._service.doc_roots(team_id=team_id)
|
|
777
933
|
if not docs:
|
|
778
934
|
return text("📚 Nenhuma raiz encontrada.")
|
|
779
935
|
body = "\n".join(
|
|
@@ -783,10 +939,11 @@ class KnowledgeTool(Tool):
|
|
|
783
939
|
return text(f"📚 **Raízes de documentação:**\n{body}")
|
|
784
940
|
|
|
785
941
|
if action is KnowledgeAction.DOC_RECENT:
|
|
786
|
-
|
|
942
|
+
team_id = sanitize_null(payload.doc_team_id or payload.team_id)
|
|
943
|
+
if not team_id:
|
|
787
944
|
return text("❌ Informe team_id para listar documentos recentes.")
|
|
788
945
|
docs = await self._service.doc_recent(
|
|
789
|
-
team_id=
|
|
946
|
+
team_id=team_id,
|
|
790
947
|
limit=payload.limit,
|
|
791
948
|
)
|
|
792
949
|
if not docs:
|
|
@@ -795,11 +952,10 @@ class KnowledgeTool(Tool):
|
|
|
795
952
|
return text(f"🕒 **Documentos recentes ({len(docs)}):**\n\n{body}")
|
|
796
953
|
|
|
797
954
|
if action is KnowledgeAction.DOC_ANALYTICS:
|
|
798
|
-
|
|
955
|
+
team_id = sanitize_null(payload.doc_team_id or payload.team_id)
|
|
956
|
+
if not team_id:
|
|
799
957
|
return text("❌ Informe team_id para obter analytics.")
|
|
800
|
-
analytics = await self._service.doc_analytics(
|
|
801
|
-
team_id=payload.doc_team_id or payload.team_id
|
|
802
|
-
)
|
|
958
|
+
analytics = await self._service.doc_analytics(team_id=team_id)
|
|
803
959
|
lines = ["📊 **Analytics de Documentação**"]
|
|
804
960
|
for key, value in analytics.items():
|
|
805
961
|
lines.append(f"- {key}: {value}")
|
|
@@ -932,6 +1088,10 @@ def _format_work(item: Dict[str, Any], *, header: Optional[str] = None) -> str:
|
|
|
932
1088
|
lines.append(
|
|
933
1089
|
f"Vencimento: {_format_date(item.get('due_date') or item.get('dueDate'))}"
|
|
934
1090
|
)
|
|
1091
|
+
if item.get("tags"):
|
|
1092
|
+
tags = item.get("tags", [])
|
|
1093
|
+
if tags:
|
|
1094
|
+
lines.append(f"Tags: {', '.join(tags)}")
|
|
935
1095
|
return "\n".join(lines)
|
|
936
1096
|
|
|
937
1097
|
|
|
@@ -9,7 +9,16 @@ from typing import Any, Dict, List, Optional
|
|
|
9
9
|
from pydantic import Field
|
|
10
10
|
|
|
11
11
|
from fenix_mcp.application.presenters import text
|
|
12
|
-
from fenix_mcp.application.tool_base import
|
|
12
|
+
from fenix_mcp.application.tool_base import (
|
|
13
|
+
CategoryStr,
|
|
14
|
+
DateTimeStr,
|
|
15
|
+
MarkdownStr,
|
|
16
|
+
TagStr,
|
|
17
|
+
TitleStr,
|
|
18
|
+
Tool,
|
|
19
|
+
ToolRequest,
|
|
20
|
+
UUIDStr,
|
|
21
|
+
)
|
|
13
22
|
from fenix_mcp.domain.productivity import ProductivityService
|
|
14
23
|
from fenix_mcp.infrastructure.context import AppContext
|
|
15
24
|
|
|
@@ -59,19 +68,26 @@ ACTION_FIELD_DESCRIPTION = (
|
|
|
59
68
|
|
|
60
69
|
class ProductivityRequest(ToolRequest):
|
|
61
70
|
action: TodoAction = Field(description=ACTION_FIELD_DESCRIPTION)
|
|
62
|
-
id: Optional[
|
|
63
|
-
title: Optional[
|
|
71
|
+
id: Optional[UUIDStr] = Field(default=None, description="ID do item TODO (UUID).")
|
|
72
|
+
title: Optional[TitleStr] = Field(
|
|
64
73
|
default=None, description="Título do TODO (obrigatório em create)."
|
|
65
74
|
)
|
|
66
|
-
content: Optional[
|
|
75
|
+
content: Optional[MarkdownStr] = Field(
|
|
67
76
|
default=None, description="Conteúdo em Markdown (obrigatório em create)."
|
|
68
77
|
)
|
|
69
|
-
status: Optional[str] = Field(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
default=None, description="
|
|
78
|
+
status: Optional[str] = Field(
|
|
79
|
+
default=None,
|
|
80
|
+
description="Status do TODO (pending, in_progress, completed, cancelled).",
|
|
81
|
+
)
|
|
82
|
+
priority: Optional[str] = Field(
|
|
83
|
+
default=None, description="Prioridade do TODO (low, medium, high, urgent)."
|
|
84
|
+
)
|
|
85
|
+
category: Optional[CategoryStr] = Field(
|
|
86
|
+
default=None, description="Categoria opcional."
|
|
87
|
+
)
|
|
88
|
+
tags: Optional[List[TagStr]] = Field(default=None, description="Lista de tags.")
|
|
89
|
+
due_date: Optional[DateTimeStr] = Field(
|
|
90
|
+
default=None, description="Data de vencimento do TODO (ISO 8601)."
|
|
75
91
|
)
|
|
76
92
|
limit: int = Field(
|
|
77
93
|
default=20, ge=1, le=100, description="Limite de resultados em list/search."
|
|
@@ -9,7 +9,13 @@ from typing import Any, Dict, List, Optional
|
|
|
9
9
|
from pydantic import Field
|
|
10
10
|
|
|
11
11
|
from fenix_mcp.application.presenters import text
|
|
12
|
-
from fenix_mcp.application.tool_base import
|
|
12
|
+
from fenix_mcp.application.tool_base import (
|
|
13
|
+
MarkdownStr,
|
|
14
|
+
TitleStr,
|
|
15
|
+
Tool,
|
|
16
|
+
ToolRequest,
|
|
17
|
+
UUIDStr,
|
|
18
|
+
)
|
|
13
19
|
from fenix_mcp.domain.user_config import UserConfigService, _strip_none
|
|
14
20
|
from fenix_mcp.infrastructure.context import AppContext
|
|
15
21
|
|
|
@@ -51,15 +57,20 @@ ACTION_FIELD_DESCRIPTION = "Ação a executar. Escolha um dos valores: " + ", ".
|
|
|
51
57
|
|
|
52
58
|
class UserConfigRequest(ToolRequest):
|
|
53
59
|
action: UserConfigAction = Field(description=ACTION_FIELD_DESCRIPTION)
|
|
54
|
-
id: Optional[
|
|
55
|
-
name: Optional[
|
|
56
|
-
content: Optional[
|
|
60
|
+
id: Optional[UUIDStr] = Field(default=None, description="ID do documento (UUID).")
|
|
61
|
+
name: Optional[TitleStr] = Field(default=None, description="Nome do documento.")
|
|
62
|
+
content: Optional[MarkdownStr] = Field(
|
|
57
63
|
default=None, description="Conteúdo em Markdown/JSON."
|
|
58
64
|
)
|
|
59
|
-
mode_id: Optional[
|
|
65
|
+
mode_id: Optional[UUIDStr] = Field(
|
|
66
|
+
default=None, description="ID do modo associado (UUID)."
|
|
67
|
+
)
|
|
60
68
|
is_default: Optional[bool] = Field(
|
|
61
69
|
default=None, description="Marca o documento como padrão."
|
|
62
70
|
)
|
|
71
|
+
metadata: Optional[MarkdownStr] = Field(
|
|
72
|
+
default=None, description="Metadata estruturada do documento (Markdown)."
|
|
73
|
+
)
|
|
63
74
|
limit: int = Field(default=20, ge=1, le=100, description="Limite para listagem.")
|
|
64
75
|
offset: int = Field(default=0, ge=0, description="Offset para listagem.")
|
|
65
76
|
return_content: Optional[bool] = Field(
|
|
@@ -90,6 +101,7 @@ class UserConfigTool(Tool):
|
|
|
90
101
|
"content": payload.content,
|
|
91
102
|
"mode_id": payload.mode_id,
|
|
92
103
|
"is_default": payload.is_default,
|
|
104
|
+
"metadata": payload.metadata,
|
|
93
105
|
}
|
|
94
106
|
)
|
|
95
107
|
)
|
|
@@ -124,6 +136,7 @@ class UserConfigTool(Tool):
|
|
|
124
136
|
"content": payload.content,
|
|
125
137
|
"mode_id": payload.mode_id,
|
|
126
138
|
"is_default": payload.is_default,
|
|
139
|
+
"metadata": payload.metadata,
|
|
127
140
|
}
|
|
128
141
|
)
|
|
129
142
|
doc = await self._service.update(payload.id, data)
|
fenix_mcp/domain/intelligence.py
CHANGED
|
@@ -114,36 +114,6 @@ class IntelligenceService:
|
|
|
114
114
|
}
|
|
115
115
|
return await self._call(self.api.consolidate_memories, payload)
|
|
116
116
|
|
|
117
|
-
async def priority_memories(self, *, limit: int) -> List[Dict[str, Any]]:
|
|
118
|
-
params = {
|
|
119
|
-
"limit": limit,
|
|
120
|
-
"sortBy": "priority_score",
|
|
121
|
-
"sortOrder": "desc",
|
|
122
|
-
}
|
|
123
|
-
return (
|
|
124
|
-
await self._call(
|
|
125
|
-
self.api.list_memories,
|
|
126
|
-
include_content=False,
|
|
127
|
-
include_metadata=False,
|
|
128
|
-
**params,
|
|
129
|
-
)
|
|
130
|
-
or []
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
async def analytics(self, *, time_range: str, group_by: str) -> Dict[str, Any]:
|
|
134
|
-
memories = await self.query_memories(
|
|
135
|
-
limit=200, timeRange=time_range, groupBy=group_by
|
|
136
|
-
)
|
|
137
|
-
summary: Dict[str, Any] = {
|
|
138
|
-
"total_memories": len(memories),
|
|
139
|
-
"by_group": {},
|
|
140
|
-
}
|
|
141
|
-
group_key = group_by
|
|
142
|
-
for memory in memories:
|
|
143
|
-
key = memory.get(group_key) or memory.get("metadata") or "N/A"
|
|
144
|
-
summary["by_group"][key] = summary["by_group"].get(key, 0) + 1
|
|
145
|
-
return summary
|
|
146
|
-
|
|
147
117
|
async def update_memory(self, memory_id: str, **fields: Any) -> Dict[str, Any]:
|
|
148
118
|
payload = _strip_none(fields)
|
|
149
119
|
if "importance" in payload:
|
|
@@ -162,6 +132,9 @@ class IntelligenceService:
|
|
|
162
132
|
payload[new_key] = payload.pop(old_key)
|
|
163
133
|
return await self._call(self.api.update_memory, memory_id, payload)
|
|
164
134
|
|
|
135
|
+
async def delete_memory(self, memory_id: str) -> None:
|
|
136
|
+
await self._call(self.api.delete_memory, memory_id)
|
|
137
|
+
|
|
165
138
|
async def _call(self, func, *args, **kwargs):
|
|
166
139
|
return await asyncio.to_thread(func, *args, **kwargs)
|
|
167
140
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
fenix_mcp/__init__.py,sha256=
|
|
1
|
+
fenix_mcp/__init__.py,sha256=CSPCawyB8PqAznV1-_PCd3yaF-QxADM8AL5YGnaR0Oc,180
|
|
2
2
|
fenix_mcp/main.py,sha256=iJV-9btNMDJMObvcn7wBQdbLLKjkYCQ1ANGEwHGHlMU,2857
|
|
3
3
|
fenix_mcp/application/presenters.py,sha256=fGME54PdCDhTBhXO-JUB9yLdBHiE1aeXLTC2fCuxnxM,689
|
|
4
|
-
fenix_mcp/application/tool_base.py,sha256=
|
|
4
|
+
fenix_mcp/application/tool_base.py,sha256=YJk7aSVGjXEvAkXrOHOuUjCFhYni9NPKFyPKiZqkrCc,4235
|
|
5
5
|
fenix_mcp/application/tool_registry.py,sha256=bPT5g8GfxG_qu28R1WaDOZHvtmG6TPDvZi8eWj1T9xE,1250
|
|
6
6
|
fenix_mcp/application/tools/__init__.py,sha256=Gi1YvYh-KdL9HD8gLVrknHrxiKKEOhHBEZ02KBXJaKQ,796
|
|
7
7
|
fenix_mcp/application/tools/health.py,sha256=m5DxhoRbdwl6INzd6PISxv1NAv-ljCrezsr773VB0wE,834
|
|
8
|
-
fenix_mcp/application/tools/initialize.py,sha256=
|
|
9
|
-
fenix_mcp/application/tools/intelligence.py,sha256=
|
|
10
|
-
fenix_mcp/application/tools/knowledge.py,sha256=
|
|
11
|
-
fenix_mcp/application/tools/productivity.py,sha256=
|
|
12
|
-
fenix_mcp/application/tools/user_config.py,sha256=
|
|
8
|
+
fenix_mcp/application/tools/initialize.py,sha256=rJ3avvF-dulevQ6eFKBJVLDhfJDARLaMV5wAn2hGZoc,5638
|
|
9
|
+
fenix_mcp/application/tools/intelligence.py,sha256=riGKd7w3pWvzu3q80L5UmWfFr6WcbF3si39DAakMwzk,15374
|
|
10
|
+
fenix_mcp/application/tools/knowledge.py,sha256=y8SfizAEVdbkfJ0ueJOG6ZnsXmC_P9ymJXDxS7gA7jU,51069
|
|
11
|
+
fenix_mcp/application/tools/productivity.py,sha256=T4RCl0hjDKrY8llMM1GCoiP4YwO_HPnPY5R2gIJRx04,10228
|
|
12
|
+
fenix_mcp/application/tools/user_config.py,sha256=Y5XJVs7w_13FtFqUzK3vJyEQvHvuBCzKtqu0mRVVkPA,6573
|
|
13
13
|
fenix_mcp/domain/initialization.py,sha256=AZhdSNITQ7O3clELBuqGvjJc-c8pFKc7zQz-XR2xXPc,6933
|
|
14
|
-
fenix_mcp/domain/intelligence.py,sha256=
|
|
14
|
+
fenix_mcp/domain/intelligence.py,sha256=j1kkxT-pjuzLQeAGDd2H8gd3O1aeUIRgHFnMGvNwQYg,8636
|
|
15
15
|
fenix_mcp/domain/knowledge.py,sha256=IpfHgxxsXycpJKnlAHcfTSHkp7ycBq4o7J-GIU8Xmv0,20246
|
|
16
16
|
fenix_mcp/domain/productivity.py,sha256=nmHRuVJGRRu1s4eMoAv8vXHKsSauCPl-FvFx3I_yCTE,6661
|
|
17
17
|
fenix_mcp/domain/user_config.py,sha256=LzBDCk31gLMtKHTbBmYb9VoFPHDW6OydpmDXeHHd0Mw,1642
|
|
@@ -22,8 +22,8 @@ fenix_mcp/infrastructure/logging.py,sha256=bHrWlSi_0HshRe3--BK_5nzUszW-gh37q6jsd
|
|
|
22
22
|
fenix_mcp/infrastructure/fenix_api/client.py,sha256=Navi7cGAOradghcbYkFIQQINpjFrdINlNhdfZ4iSSYQ,28338
|
|
23
23
|
fenix_mcp/interface/mcp_server.py,sha256=5UM2NJuNbwHkmCEprIFataJ5nFZiO8efTtP_oW3_iX0,2331
|
|
24
24
|
fenix_mcp/interface/transports.py,sha256=PxdhfjH8UMl03f7nuCLc-M6tMx6-Y-btVz_mSqXKrSI,8138
|
|
25
|
-
fenix_mcp-0.
|
|
26
|
-
fenix_mcp-0.
|
|
27
|
-
fenix_mcp-0.
|
|
28
|
-
fenix_mcp-0.
|
|
29
|
-
fenix_mcp-0.
|
|
25
|
+
fenix_mcp-0.7.0.dist-info/METADATA,sha256=6vrhVJyzywkUrYa0xolIiTvpyqFxznhyIBfSvPCimCM,7260
|
|
26
|
+
fenix_mcp-0.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
27
|
+
fenix_mcp-0.7.0.dist-info/entry_points.txt,sha256=o52x_YHBupEd-1Z1GSfUjv3gJrx5_I-EkHhCgt1WBaE,49
|
|
28
|
+
fenix_mcp-0.7.0.dist-info/top_level.txt,sha256=2G1UtKpwjaIGQyE7sRoHecxaGLeuexfjrOUjv9DDKh4,10
|
|
29
|
+
fenix_mcp-0.7.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|