fenix-mcp 0.7.0__py3-none-any.whl → 1.1.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.
@@ -39,90 +39,96 @@ class KnowledgeAction(str, Enum):
39
39
  # Work items
40
40
  WORK_CREATE = (
41
41
  "work_create",
42
- "Cria um work item com título, status e vínculos opcionais.",
42
+ "Creates a work item with title, status and optional links.",
43
43
  )
44
44
  WORK_LIST = (
45
45
  "work_list",
46
- "Lista work items com filtros de status, prioridade e contexto.",
46
+ "Lists work items with status, priority and context filters.",
47
47
  )
48
- WORK_GET = ("work_get", "Obtém detalhes completos de um work item pelo ID.")
48
+ WORK_GET = ("work_get", "Gets full details of a work item by ID.")
49
49
  WORK_UPDATE = (
50
50
  "work_update",
51
- "Atualiza campos específicos de um work item existente.",
51
+ "Updates specific fields of an existing work item.",
52
52
  )
53
- WORK_DELETE = ("work_delete", "Remove um work item definitivamente.")
54
- WORK_BACKLOG = ("work_backlog", "Lista itens do backlog de um time.")
55
- WORK_SEARCH = ("work_search", "Busca work items por texto com filtros adicionais.")
56
- WORK_ANALYTICS = ("work_analytics", "Retorna métricas consolidadas de work items.")
57
- WORK_BY_BOARD = ("work_by_board", "Lista work items associados a um board.")
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.")
53
+ WORK_DELETE = ("work_delete", "Permanently removes a work item.")
54
+ WORK_BACKLOG = ("work_backlog", "Lists backlog items for a team.")
55
+ WORK_SEARCH = (
56
+ "work_search",
57
+ "Searches work items by text with additional filters.",
58
+ )
59
+ WORK_ANALYTICS = ("work_analytics", "Returns consolidated work item metrics.")
60
+ WORK_BY_BOARD = ("work_by_board", "Lists work items associated with a board.")
61
+ WORK_BY_SPRINT = ("work_by_sprint", "Lists work items associated with a sprint.")
62
+ WORK_BY_EPIC = ("work_by_epic", "Lists work items associated with an epic.")
63
+ WORK_CHILDREN = ("work_children", "Lists child work items of a parent item.")
61
64
  WORK_STATUS_UPDATE = (
62
65
  "work_status_update",
63
- "Atualiza apenas o status de um work item.",
66
+ "Updates only the status of a work item.",
64
67
  )
65
68
  WORK_ASSIGN_SPRINT = (
66
69
  "work_assign_sprint",
67
- "Atribui work items a um sprint.",
70
+ "Assigns work items to a sprint.",
68
71
  )
69
72
 
70
73
  # Boards
71
- BOARD_LIST = ("board_list", "Lista boards disponíveis com filtros opcionais.")
72
- BOARD_BY_TEAM = ("board_by_team", "Lista boards de um time específico.")
73
- BOARD_FAVORITES = ("board_favorites", "Lista boards marcados como favoritos.")
74
- BOARD_GET = ("board_get", "Obtém detalhes de um board pelo ID.")
75
- BOARD_COLUMNS = ("board_columns", "Lista colunas configuradas para um board.")
74
+ BOARD_LIST = ("board_list", "Lists available boards with optional filters.")
75
+ BOARD_BY_TEAM = ("board_by_team", "Lists boards for a specific team.")
76
+ BOARD_FAVORITES = ("board_favorites", "Lists boards marked as favorites.")
77
+ BOARD_GET = ("board_get", "Gets board details by ID.")
78
+ BOARD_COLUMNS = ("board_columns", "Lists columns configured for a board.")
76
79
 
77
80
  # Sprints
78
- SPRINT_LIST = ("sprint_list", "Lista sprints disponíveis com filtros opcionais.")
79
- SPRINT_BY_TEAM = ("sprint_by_team", "Lista sprints associados a um time.")
80
- SPRINT_ACTIVE = ("sprint_active", "Obtém o sprint ativo de um time.")
81
- SPRINT_GET = ("sprint_get", "Obtém detalhes de um sprint pelo ID.")
81
+ SPRINT_LIST = ("sprint_list", "Lists available sprints with optional filters.")
82
+ SPRINT_BY_TEAM = ("sprint_by_team", "Lists sprints associated with a team.")
83
+ SPRINT_ACTIVE = ("sprint_active", "Gets the active sprint for a team.")
84
+ SPRINT_GET = ("sprint_get", "Gets sprint details by ID.")
82
85
  SPRINT_WORK_ITEMS = (
83
86
  "sprint_work_items",
84
- "Lista work items vinculados a um sprint.",
87
+ "Lists work items linked to a sprint.",
85
88
  )
86
89
 
87
90
  # Modes
88
- MODE_CREATE = ("mode_create", "Cria um modo com conteúdo e metadados opcionais.")
89
- MODE_LIST = ("mode_list", "Lista modes cadastrados.")
90
- MODE_GET = ("mode_get", "Obtém detalhes completos de um modo.")
91
- MODE_UPDATE = ("mode_update", "Atualiza propriedades de um modo existente.")
92
- MODE_DELETE = ("mode_delete", "Remove um modo.")
93
- MODE_RULE_ADD = ("mode_rule_add", "Associa uma regra a um modo.")
91
+ MODE_CREATE = ("mode_create", "Creates a mode with content and optional metadata.")
92
+ MODE_LIST = ("mode_list", "Lists registered modes.")
93
+ MODE_GET = ("mode_get", "Gets full details of a mode.")
94
+ MODE_UPDATE = ("mode_update", "Updates properties of an existing mode.")
95
+ MODE_DELETE = ("mode_delete", "Removes a mode.")
96
+ MODE_RULE_ADD = ("mode_rule_add", "Associates a rule with a mode.")
94
97
  MODE_RULE_REMOVE = (
95
98
  "mode_rule_remove",
96
- "Remove a associação de uma regra com um modo.",
99
+ "Removes the association of a rule with a mode.",
97
100
  )
98
- MODE_RULES = ("mode_rules", "Lista regras associadas a um modo.")
101
+ MODE_RULES = ("mode_rules", "Lists rules associated with a mode.")
99
102
 
100
103
  # Rules
101
- RULE_CREATE = ("rule_create", "Cria uma regra com conteúdo e metadados.")
102
- RULE_LIST = ("rule_list", "Lista regras cadastradas.")
103
- RULE_GET = ("rule_get", "Obtém detalhes de uma regra.")
104
- RULE_UPDATE = ("rule_update", "Atualiza uma regra existente.")
105
- RULE_DELETE = ("rule_delete", "Remove uma regra.")
104
+ RULE_CREATE = ("rule_create", "Creates a rule with content and metadata.")
105
+ RULE_LIST = ("rule_list", "Lists registered rules.")
106
+ RULE_GET = ("rule_get", "Gets rule details.")
107
+ RULE_UPDATE = ("rule_update", "Updates an existing rule.")
108
+ RULE_DELETE = ("rule_delete", "Removes a rule.")
106
109
 
107
110
  # Documentation
108
- DOC_CREATE = ("doc_create", "Cria um item de documentação.")
109
- DOC_LIST = ("doc_list", "Lista itens de documentação com filtros.")
110
- DOC_GET = ("doc_get", "Obtém detalhes de um item de documentação.")
111
- DOC_UPDATE = ("doc_update", "Atualiza um item de documentação.")
112
- DOC_DELETE = ("doc_delete", "Remove um item de documentação.")
113
- DOC_SEARCH = ("doc_search", "Busca itens de documentação por texto.")
114
- DOC_ROOTS = ("doc_roots", "Lista documentos raiz disponíveis.")
115
- DOC_RECENT = ("doc_recent", "Lista documentos acessados recentemente.")
116
- DOC_ANALYTICS = ("doc_analytics", "Retorna analytics dos documentos.")
117
- DOC_CHILDREN = ("doc_children", "Lista documentos filhos de um item.")
118
- DOC_TREE = ("doc_tree", "Recupera a árvore direta de um documento.")
119
- DOC_FULL_TREE = ("doc_full_tree", "Recupera a árvore completa de documentação.")
120
- DOC_MOVE = ("doc_move", "Move um documento para outro pai.")
121
- DOC_PUBLISH = ("doc_publish", "Altera status de publicação de um documento.")
122
- DOC_VERSION = ("doc_version", "Gera ou recupera versão de um documento.")
123
- DOC_DUPLICATE = ("doc_duplicate", "Duplica um documento existente.")
124
-
125
- HELP = ("knowledge_help", "Mostra as ações disponíveis e seus usos.")
111
+ DOC_CREATE = (
112
+ "doc_create",
113
+ "Creates a documentation item. Requires doc_emoji for page, api_doc, and guide types.",
114
+ )
115
+ DOC_LIST = ("doc_list", "Lists documentation items with filters.")
116
+ DOC_GET = ("doc_get", "Gets documentation item details.")
117
+ DOC_UPDATE = ("doc_update", "Updates a documentation item.")
118
+ DOC_DELETE = ("doc_delete", "Removes a documentation item.")
119
+ DOC_SEARCH = ("doc_search", "Searches documentation items by text.")
120
+ DOC_ROOTS = ("doc_roots", "Lists available root documents.")
121
+ DOC_RECENT = ("doc_recent", "Lists recently accessed documents.")
122
+ DOC_ANALYTICS = ("doc_analytics", "Returns document analytics.")
123
+ DOC_CHILDREN = ("doc_children", "Lists child documents of an item.")
124
+ DOC_TREE = ("doc_tree", "Retrieves the direct tree of a document.")
125
+ DOC_FULL_TREE = ("doc_full_tree", "Retrieves the full documentation tree.")
126
+ DOC_MOVE = ("doc_move", "Moves a document to another parent.")
127
+ DOC_PUBLISH = ("doc_publish", "Changes publication status of a document.")
128
+ DOC_VERSION = ("doc_version", "Generates or retrieves a document version.")
129
+ DOC_DUPLICATE = ("doc_duplicate", "Duplicates an existing document.")
130
+
131
+ HELP = ("knowledge_help", "Shows available actions and their uses.")
126
132
 
127
133
  @classmethod
128
134
  def choices(cls) -> List[str]:
@@ -131,7 +137,7 @@ class KnowledgeAction(str, Enum):
131
137
  @classmethod
132
138
  def formatted_help(cls) -> str:
133
139
  lines = [
134
- "| **Ação** | **Descrição** |",
140
+ "| **Action** | **Description** |",
135
141
  "| --- | --- |",
136
142
  ]
137
143
  for member in cls:
@@ -139,7 +145,7 @@ class KnowledgeAction(str, Enum):
139
145
  return "\n".join(lines)
140
146
 
141
147
 
142
- ACTION_FIELD_DESCRIPTION = "Ação de conhecimento. Escolha um dos valores: " + ", ".join(
148
+ ACTION_FIELD_DESCRIPTION = "Knowledge action. Choose one of the values: " + ", ".join(
143
149
  f"`{member.value}` ({member.description.rstrip('.')})."
144
150
  for member in KnowledgeAction
145
151
  )
@@ -156,165 +162,159 @@ _ALLOWED_DOC_TYPES = {
156
162
  class KnowledgeRequest(ToolRequest):
157
163
  action: KnowledgeAction = Field(description=ACTION_FIELD_DESCRIPTION)
158
164
  id: Optional[UUIDStr] = Field(
159
- default=None, description="ID principal do recurso (UUID)."
165
+ default=None, description="Primary resource ID (UUID)."
160
166
  )
161
- limit: int = Field(default=20, ge=1, le=100, description="Limite de resultados.")
167
+ limit: int = Field(default=20, ge=1, le=100, description="Result limit.")
162
168
  offset: int = Field(
163
- default=0, ge=0, description="Offset de paginação (quando suportado)."
164
- )
165
- team_id: Optional[UUIDStr] = Field(
166
- default=None, description="ID do time para filtros (UUID)."
169
+ default=0, ge=0, description="Pagination offset (when supported)."
167
170
  )
168
171
  board_id: Optional[UUIDStr] = Field(
169
- default=None, description="ID do board associado (UUID)."
172
+ default=None, description="Associated board ID (UUID)."
170
173
  )
171
174
  sprint_id: Optional[UUIDStr] = Field(
172
- default=None, description="ID do sprint associado (UUID)."
175
+ default=None, description="Associated sprint ID (UUID)."
173
176
  )
174
177
  epic_id: Optional[UUIDStr] = Field(
175
- default=None, description="ID do épico associado (UUID)."
178
+ default=None, description="Associated epic ID (UUID)."
176
179
  )
177
- query: Optional[str] = Field(default=None, description="Filtro/busca.")
180
+ query: Optional[str] = Field(default=None, description="Filter/search term.")
178
181
  return_content: Optional[bool] = Field(
179
- default=None, description="Retorna conteúdo completo."
182
+ default=None, description="Return full content."
180
183
  )
181
184
  return_description: Optional[bool] = Field(
182
- default=None, description="Retorna descrição completa."
185
+ default=None, description="Return full description."
183
186
  )
184
187
  return_metadata: Optional[bool] = Field(
185
- default=None, description="Retorna metadados completos."
188
+ default=None, description="Return full metadata."
186
189
  )
187
190
 
188
191
  # Work item fields
189
- work_title: Optional[TitleStr] = Field(
190
- default=None, description="Título do work item."
191
- )
192
+ work_title: Optional[TitleStr] = Field(default=None, description="Work item title.")
192
193
  work_description: Optional[MarkdownStr] = Field(
193
- default=None, description="Descrição do work item (Markdown)."
194
+ default=None, description="Work item description (Markdown)."
194
195
  )
195
196
  work_type: Optional[str] = Field(
196
197
  default="task",
197
- description="Tipo do work item (epic, feature, story, task, subtask, bug).",
198
+ description="Work item type (epic, feature, story, task, subtask, bug).",
198
199
  )
199
200
  work_status: Optional[str] = Field(
200
201
  default=None,
201
- description="Status do work item (backlog, todo, in_progress, review, testing, done, cancelled).",
202
+ description="Work item status (backlog, todo, in_progress, review, testing, done, cancelled).",
202
203
  )
203
204
  work_priority: Optional[str] = Field(
204
205
  default=None,
205
- description="Prioridade do work item (critical, high, medium, low).",
206
+ description="Work item priority (critical, high, medium, low).",
206
207
  )
207
208
  story_points: Optional[int] = Field(
208
209
  default=None, ge=0, le=100, description="Story points (0-100)."
209
210
  )
210
211
  assignee_id: Optional[UUIDStr] = Field(
211
- default=None, description="ID do responsável (UUID)."
212
+ default=None, description="Assignee ID (UUID)."
212
213
  )
213
214
  parent_id: Optional[UUIDStr] = Field(
214
- default=None, description="ID do item pai (UUID)."
215
+ default=None, description="Parent item ID (UUID)."
215
216
  )
216
217
  work_due_date: Optional[DateTimeStr] = Field(
217
- default=None, description="Data de vencimento do work item (ISO 8601)."
218
+ default=None, description="Work item due date (ISO 8601)."
218
219
  )
219
220
  work_tags: Optional[List[TagStr]] = Field(
220
- default=None, description="Tags do work item."
221
+ default=None, description="Work item tags."
221
222
  )
222
223
  work_item_ids: Optional[List[UUIDStr]] = Field(
223
224
  default=None,
224
- description="Lista de IDs de work items para operações em lote (UUIDs).",
225
+ description="List of work item IDs for batch operations (UUIDs).",
225
226
  )
226
227
 
227
228
  # Mode fields
228
229
  mode_id: Optional[UUIDStr] = Field(
229
- default=None, description="ID do modo relacionado (UUID)."
230
+ default=None, description="Related mode ID (UUID)."
230
231
  )
231
- mode_name: Optional[TitleStr] = Field(default=None, description="Nome do modo.")
232
+ mode_name: Optional[TitleStr] = Field(default=None, description="Mode name.")
232
233
  mode_description: Optional[DescriptionStr] = Field(
233
- default=None, description="Descrição do modo."
234
+ default=None, description="Mode description."
234
235
  )
235
236
  mode_content: Optional[MarkdownStr] = Field(
236
- default=None, description="Conteúdo do modo (Markdown)."
237
+ default=None, description="Mode content (Markdown)."
237
238
  )
238
239
  mode_is_default: Optional[bool] = Field(
239
- default=None, description="Indica se o modo é padrão."
240
+ default=None, description="Indicates if the mode is default."
240
241
  )
241
242
  mode_metadata: Optional[MarkdownStr] = Field(
242
- default=None, description="Metadata do modo para processamento de IA."
243
+ default=None, description="Mode metadata for AI processing."
243
244
  )
244
245
 
245
246
  # Rule fields
246
247
  rule_id: Optional[UUIDStr] = Field(
247
- default=None, description="ID da regra relacionada (UUID)."
248
+ default=None, description="Related rule ID (UUID)."
248
249
  )
249
- rule_name: Optional[TitleStr] = Field(default=None, description="Nome da regra.")
250
+ rule_name: Optional[TitleStr] = Field(default=None, description="Rule name.")
250
251
  rule_description: Optional[DescriptionStr] = Field(
251
- default=None, description="Descrição da regra."
252
+ default=None, description="Rule description."
252
253
  )
253
254
  rule_content: Optional[MarkdownStr] = Field(
254
- default=None, description="Conteúdo da regra (Markdown)."
255
+ default=None, description="Rule content (Markdown)."
255
256
  )
256
- rule_is_default: Optional[bool] = Field(default=None, description="Regra padrão.")
257
+ rule_is_default: Optional[bool] = Field(default=None, description="Default rule.")
257
258
  rule_metadata: Optional[MarkdownStr] = Field(
258
- default=None, description="Metadata da regra para processamento de IA."
259
+ default=None, description="Rule metadata for AI processing."
259
260
  )
260
261
 
261
262
  # Documentation fields
262
263
  doc_title: Optional[TitleStr] = Field(
263
- default=None, description="Título da documentação."
264
+ default=None, description="Documentation title."
264
265
  )
265
266
  doc_description: Optional[DescriptionStr] = Field(
266
- default=None, description="Descrição da documentação."
267
+ default=None, description="Documentation description."
267
268
  )
268
269
  doc_content: Optional[MarkdownStr] = Field(
269
- default=None, description="Conteúdo da documentação (Markdown)."
270
+ default=None, description="Documentation content (Markdown)."
270
271
  )
271
272
  doc_status: Optional[str] = Field(
272
273
  default=None,
273
- description="Status da documentação (draft, review, published, archived).",
274
+ description="Documentation status (draft, review, published, archived).",
274
275
  )
275
276
  doc_type: Optional[str] = Field(
276
- default=None, description="Tipo da documentação (folder, page, api_doc, guide)."
277
+ default=None,
278
+ description="Documentation type. Values: folder, page, api_doc, guide.",
277
279
  )
278
280
  doc_language: Optional[LanguageStr] = Field(
279
- default=None, description="Idioma da documentação (ex: pt, en, pt-BR)."
281
+ default=None, description="Documentation language (e.g.: pt, en, pt-BR)."
280
282
  )
281
283
  doc_parent_id: Optional[UUIDStr] = Field(
282
- default=None, description="ID do documento pai (UUID)."
283
- )
284
- doc_team_id: Optional[UUIDStr] = Field(
285
- default=None, description="ID do time responsável pela documentação (UUID)."
284
+ default=None, description="Parent document ID (UUID)."
286
285
  )
287
286
  doc_owner_id: Optional[UUIDStr] = Field(
288
- default=None, description="ID do dono (UUID)."
287
+ default=None, description="Owner ID (UUID)."
289
288
  )
290
289
  doc_reviewer_id: Optional[UUIDStr] = Field(
291
- default=None, description="ID do revisor (UUID)."
290
+ default=None, description="Reviewer ID (UUID)."
292
291
  )
293
292
  doc_version: Optional[VersionStr] = Field(
294
- default=None, description="Versão do documento (ex: 1.0, 2.1.3)."
293
+ default=None, description="Document version (e.g.: 1.0, 2.1.3)."
295
294
  )
296
- doc_category: Optional[CategoryStr] = Field(default=None, description="Categoria.")
295
+ doc_category: Optional[CategoryStr] = Field(default=None, description="Category.")
297
296
  doc_tags: Optional[List[TagStr]] = Field(default=None, description="Tags.")
298
297
  doc_position: Optional[int] = Field(
299
- default=None, ge=0, description="Posição desejada ao mover documentos."
298
+ default=None, ge=0, description="Desired position when moving documents."
300
299
  )
301
300
  doc_emoji: Optional[EmojiStr] = Field(
302
- default=None, description="Emoji exibido junto ao documento."
301
+ default=None,
302
+ description="Emoji displayed with the document. REQUIRED for page, api_doc, and guide types (not required for folder).",
303
303
  )
304
304
  doc_emote: Optional[EmojiStr] = Field(
305
- default=None, description="Alias para emoji, mantido por compatibilidade."
305
+ default=None, description="Alias for emoji, kept for compatibility."
306
306
  )
307
307
  doc_keywords: Optional[List[TagStr]] = Field(
308
- default=None, description="Palavras-chave para busca."
308
+ default=None, description="Keywords for search."
309
309
  )
310
310
  doc_is_public: Optional[bool] = Field(
311
- default=None, description="Se o documento é público."
311
+ default=None, description="Whether the document is public."
312
312
  )
313
313
 
314
314
 
315
315
  class KnowledgeTool(Tool):
316
316
  name = "knowledge"
317
- description = "Operações de conhecimento do Fênix Cloud (Work Items, Boards, Sprints, Modes, Rules, Docs)."
317
+ description = "Fenix Cloud knowledge operations (Work Items, Boards, Sprints, Modes, Rules, Docs)."
318
318
  request_model = KnowledgeRequest
319
319
 
320
320
  def __init__(self, context: AppContext):
@@ -338,7 +338,7 @@ class KnowledgeTool(Tool):
338
338
  if action.value.startswith("doc_"):
339
339
  return await self._run_doc(payload)
340
340
  return text(
341
- "❌ Ação inválida para knowledge.\n\nEscolha um dos valores:\n"
341
+ "❌ Invalid action for knowledge.\n\nChoose one of the values:\n"
342
342
  + "\n".join(f"- `{value}`" for value in KnowledgeAction.choices())
343
343
  )
344
344
 
@@ -349,9 +349,7 @@ class KnowledgeTool(Tool):
349
349
  action = payload.action
350
350
  if action is KnowledgeAction.WORK_CREATE:
351
351
  if not payload.work_title:
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.")
352
+ return text("❌ Provide work_title to create the item.")
355
353
  work = await self._service.work_create(
356
354
  {
357
355
  "title": payload.work_title,
@@ -364,12 +362,11 @@ class KnowledgeTool(Tool):
364
362
  "sprint_id": payload.sprint_id,
365
363
  "board_id": payload.board_id,
366
364
  "parent_id": payload.parent_id,
367
- "team_id": payload.team_id,
368
365
  "due_date": payload.work_due_date,
369
366
  "tags": payload.work_tags,
370
367
  }
371
368
  )
372
- return text(_format_work(work, header="✅ Work item criado"))
369
+ return text(_format_work(work, header="✅ Work item created"))
373
370
 
374
371
  if action is KnowledgeAction.WORK_LIST:
375
372
  items = await self._service.work_list(
@@ -382,19 +379,19 @@ class KnowledgeTool(Tool):
382
379
  board=payload.board_id,
383
380
  )
384
381
  if not items:
385
- return text("🎯 Nenhum work item encontrado.")
382
+ return text("🎯 No work items found.")
386
383
  body = "\n\n".join(_format_work(item) for item in items)
387
384
  return text(f"🎯 **Work items ({len(items)}):**\n\n{body}")
388
385
 
389
386
  if action is KnowledgeAction.WORK_GET:
390
387
  if not payload.id:
391
- return text("❌ Informe o ID do work item.")
388
+ return text("❌ Provide the work item ID.")
392
389
  work = await self._service.work_get(payload.id)
393
- return text(_format_work(work, header="🎯 Detalhes do work item"))
390
+ return text(_format_work(work, header="🎯 Work item details"))
394
391
 
395
392
  if action is KnowledgeAction.WORK_UPDATE:
396
393
  if not payload.id:
397
- return text("❌ Informe o ID do work item.")
394
+ return text("❌ Provide the work item ID.")
398
395
  work = await self._service.work_update(
399
396
  payload.id,
400
397
  {
@@ -408,106 +405,97 @@ class KnowledgeTool(Tool):
408
405
  "sprint_id": payload.sprint_id,
409
406
  "board_id": payload.board_id,
410
407
  "parent_id": payload.parent_id,
411
- "team_id": payload.team_id,
412
408
  "due_date": payload.work_due_date,
413
409
  "tags": payload.work_tags,
414
410
  },
415
411
  )
416
- return text(_format_work(work, header="✅ Work item atualizado"))
412
+ return text(_format_work(work, header="✅ Work item updated"))
417
413
 
418
414
  if action is KnowledgeAction.WORK_DELETE:
419
415
  if not payload.id:
420
- return text("❌ Informe o ID do work item.")
416
+ return text("❌ Provide the work item ID.")
421
417
  await self._service.work_delete(payload.id)
422
- return text(f"🗑️ Work item {payload.id} removido.")
418
+ return text(f"🗑️ Work item {payload.id} removed.")
423
419
 
424
420
  if action is KnowledgeAction.WORK_BACKLOG:
425
- team_id = sanitize_null(payload.team_id)
426
- if not team_id:
427
- return text("❌ Informe team_id para consultar o backlog.")
428
- items = await self._service.work_backlog(team_id=team_id)
421
+ items = await self._service.work_backlog()
429
422
  if not items:
430
- return text("📋 Backlog vazio para o time informado.")
423
+ return text("📋 Backlog empty.")
431
424
  body = "\n\n".join(_format_work(item) for item in items)
432
425
  return text(f"📋 **Backlog ({len(items)}):**\n\n{body}")
433
426
 
434
427
  if action is KnowledgeAction.WORK_SEARCH:
435
428
  query = sanitize_null(payload.query)
436
- team_id = sanitize_null(payload.team_id)
437
- if not query or not team_id:
438
- return text("❌ Informe query e team_id para buscar work items.")
429
+ if not query:
430
+ return text("❌ Provide query to search work items.")
439
431
  items = await self._service.work_search(
440
432
  query=query,
441
- team_id=team_id,
442
433
  limit=payload.limit,
443
434
  )
444
435
  if not items:
445
- return text("🔍 Nenhum work item encontrado.")
436
+ return text("🔍 No work items found.")
446
437
  body = "\n\n".join(_format_work(item) for item in items)
447
- return text(f"🔍 **Resultados ({len(items)}):**\n\n{body}")
438
+ return text(f"🔍 **Results ({len(items)}):**\n\n{body}")
448
439
 
449
440
  if action is KnowledgeAction.WORK_ANALYTICS:
450
- team_id = sanitize_null(payload.team_id)
451
- if not team_id:
452
- return text("❌ Informe team_id para obter analytics.")
453
- analytics = await self._service.work_analytics(team_id=team_id)
454
- lines = ["📊 **Analytics de Work Items**"]
441
+ analytics = await self._service.work_analytics()
442
+ lines = ["📊 **Work Items Analytics**"]
455
443
  for key, value in analytics.items():
456
444
  lines.append(f"- {key}: {value}")
457
445
  return text("\n".join(lines))
458
446
 
459
447
  if action is KnowledgeAction.WORK_BY_BOARD:
460
448
  if not payload.board_id:
461
- return text("❌ Informe board_id para listar os itens.")
449
+ return text("❌ Provide board_id to list items.")
462
450
  items = await self._service.work_by_board(board_id=payload.board_id)
463
451
  if not items:
464
- return text("🗂️ Nenhum work item para o board informado.")
452
+ return text("🗂️ No work items for the specified board.")
465
453
  body = "\n\n".join(_format_work(item) for item in items)
466
- return text(f"🗂️ **Itens do board ({len(items)}):**\n\n{body}")
454
+ return text(f"🗂️ **Board items ({len(items)}):**\n\n{body}")
467
455
 
468
456
  if action is KnowledgeAction.WORK_BY_SPRINT:
469
457
  if not payload.sprint_id:
470
- return text("❌ Informe sprint_id para listar os itens.")
458
+ return text("❌ Provide sprint_id to list items.")
471
459
  items = await self._service.work_by_sprint(sprint_id=payload.sprint_id)
472
460
  if not items:
473
- return text("🏃 Nenhum item vinculado ao sprint informado.")
461
+ return text("🏃 No items linked to the specified sprint.")
474
462
  body = "\n\n".join(_format_work(item) for item in items)
475
- return text(f"🏃 **Work items do sprint ({len(items)}):**\n\n{body}")
463
+ return text(f"🏃 **Sprint work items ({len(items)}):**\n\n{body}")
476
464
 
477
465
  if action is KnowledgeAction.WORK_BY_EPIC:
478
466
  if not payload.epic_id:
479
- return text("❌ Informe epic_id para listar os itens.")
467
+ return text("❌ Provide epic_id to list items.")
480
468
  items = await self._service.work_by_epic(epic_id=payload.epic_id)
481
469
  if not items:
482
- return text("📦 Nenhum item vinculado ao épico informado.")
470
+ return text("📦 No items linked to the specified epic.")
483
471
  body = "\n\n".join(_format_work(item) for item in items)
484
- return text(f"📦 **Work items do épico ({len(items)}):**\n\n{body}")
472
+ return text(f"📦 **Epic work items ({len(items)}):**\n\n{body}")
485
473
 
486
474
  if action is KnowledgeAction.WORK_CHILDREN:
487
475
  if not payload.id:
488
- return text("❌ Informe o ID do work item pai.")
476
+ return text("❌ Provide the parent work item ID.")
489
477
  items = await self._service.work_children(payload.id)
490
478
  if not items:
491
- return text("👶 Nenhum item filho encontrado.")
479
+ return text("👶 No child items found.")
492
480
  body = "\n\n".join(_format_work(item) for item in items)
493
- return text(f"👶 **Work items filhos ({len(items)}):**\n\n{body}")
481
+ return text(f"👶 **Child work items ({len(items)}):**\n\n{body}")
494
482
 
495
483
  if action is KnowledgeAction.WORK_STATUS_UPDATE:
496
484
  if not payload.id:
497
- return text("❌ Informe o ID do work item.")
485
+ return text("❌ Provide the work item ID.")
498
486
  if not payload.work_status:
499
- return text("❌ Informe work_status para atualizar.")
487
+ return text("❌ Provide work_status to update.")
500
488
  work = await self._service.work_update_status(
501
489
  payload.id,
502
490
  {"status": payload.work_status},
503
491
  )
504
- return text(_format_work(work, header="✅ Status atualizado"))
492
+ return text(_format_work(work, header="✅ Status updated"))
505
493
 
506
494
  if action is KnowledgeAction.WORK_ASSIGN_SPRINT:
507
495
  if not payload.sprint_id:
508
- return text("❌ Informe sprint_id para atribuir os itens.")
496
+ return text("❌ Provide sprint_id to assign items.")
509
497
  if not payload.work_item_ids:
510
- return text("❌ Informe work_item_ids com a lista de IDs.")
498
+ return text("❌ Provide work_item_ids with the list of IDs.")
511
499
  await self._service.work_assign_to_sprint(
512
500
  {
513
501
  "sprint_id": payload.sprint_id,
@@ -515,10 +503,10 @@ class KnowledgeTool(Tool):
515
503
  }
516
504
  )
517
505
  count = len(payload.work_item_ids)
518
- return text(f"✅ {count} work item(s) atribuído(s) ao sprint.")
506
+ return text(f"✅ {count} work item(s) assigned to sprint.")
519
507
 
520
508
  return text(
521
- "❌ Ação de work item não suportada.\n\nEscolha um dos valores:\n"
509
+ "❌ Unsupported work item action.\n\nChoose one of the values:\n"
522
510
  + "\n".join(
523
511
  f"- `{value}`"
524
512
  for value in KnowledgeAction.choices()
@@ -536,46 +524,44 @@ class KnowledgeTool(Tool):
536
524
  limit=payload.limit, offset=payload.offset
537
525
  )
538
526
  if not boards:
539
- return text("🗂️ Nenhum board encontrado.")
527
+ return text("🗂️ No boards found.")
540
528
  body = "\n\n".join(_format_board(board) for board in boards)
541
529
  return text(f"🗂️ **Boards ({len(boards)}):**\n\n{body}")
542
530
 
543
531
  if action is KnowledgeAction.BOARD_BY_TEAM:
544
- if not payload.team_id:
545
- return text("❌ Informe team_id para listar boards do time.")
546
- boards = await self._service.board_list_by_team(payload.team_id)
532
+ boards = await self._service.board_list_by_team()
547
533
  if not boards:
548
- return text("🗂️ Nenhum board cadastrado para o time.")
534
+ return text("🗂️ No boards registered for the team.")
549
535
  body = "\n\n".join(_format_board(board) for board in boards)
550
- return text(f"🗂️ **Boards do time ({len(boards)}):**\n\n{body}")
536
+ return text(f"🗂️ **Team boards ({len(boards)}):**\n\n{body}")
551
537
 
552
538
  if action is KnowledgeAction.BOARD_FAVORITES:
553
539
  boards = await self._service.board_favorites()
554
540
  if not boards:
555
- return text("⭐ Nenhum board favorito cadastrado.")
541
+ return text("⭐ No favorite boards registered.")
556
542
  body = "\n\n".join(_format_board(board) for board in boards)
557
- return text(f"⭐ **Boards favoritos ({len(boards)}):**\n\n{body}")
543
+ return text(f"⭐ **Favorite boards ({len(boards)}):**\n\n{body}")
558
544
 
559
545
  if action is KnowledgeAction.BOARD_GET:
560
546
  if not payload.board_id:
561
- return text("❌ Informe board_id para consultar detalhes.")
547
+ return text("❌ Provide board_id to get details.")
562
548
  board = await self._service.board_get(payload.board_id)
563
- return text(_format_board(board, header="🗂️ Detalhes do board"))
549
+ return text(_format_board(board, header="🗂️ Board details"))
564
550
 
565
551
  if action is KnowledgeAction.BOARD_COLUMNS:
566
552
  if not payload.board_id:
567
- return text("❌ Informe board_id para listar colunas.")
553
+ return text("❌ Provide board_id to list columns.")
568
554
  columns = await self._service.board_columns(payload.board_id)
569
555
  if not columns:
570
- return text("📊 Board sem colunas cadastradas.")
556
+ return text("📊 Board has no columns registered.")
571
557
  body = "\n".join(
572
- f"- {col.get('name', 'Sem nome')} (ID: {col.get('id')})"
558
+ f"- {col.get('name', 'Unnamed')} (ID: {col.get('id')})"
573
559
  for col in columns
574
560
  )
575
- return text(f"📊 **Colunas do board:**\n{body}")
561
+ return text(f"📊 **Board columns:**\n{body}")
576
562
 
577
563
  return text(
578
- "❌ Ação de board não suportada.\n\nEscolha um dos valores:\n"
564
+ "❌ Unsupported board action.\n\nChoose one of the values:\n"
579
565
  + "\n".join(
580
566
  f"- `{value}`"
581
567
  for value in KnowledgeAction.choices()
@@ -593,44 +579,40 @@ class KnowledgeTool(Tool):
593
579
  limit=payload.limit, offset=payload.offset
594
580
  )
595
581
  if not sprints:
596
- return text("🏃 Nenhum sprint encontrado.")
582
+ return text("🏃 No sprints found.")
597
583
  body = "\n\n".join(_format_sprint(sprint) for sprint in sprints)
598
584
  return text(f"🏃 **Sprints ({len(sprints)}):**\n\n{body}")
599
585
 
600
586
  if action is KnowledgeAction.SPRINT_BY_TEAM:
601
- if not payload.team_id:
602
- return text("❌ Informe team_id para listar sprints do time.")
603
- sprints = await self._service.sprint_list_by_team(payload.team_id)
587
+ sprints = await self._service.sprint_list_by_team()
604
588
  if not sprints:
605
- return text("🏃 Nenhum sprint cadastrado para o time.")
589
+ return text("🏃 No sprints registered for the team.")
606
590
  body = "\n\n".join(_format_sprint(sprint) for sprint in sprints)
607
- return text(f"🏃 **Sprints do time ({len(sprints)}):**\n\n{body}")
591
+ return text(f"🏃 **Team sprints ({len(sprints)}):**\n\n{body}")
608
592
 
609
593
  if action is KnowledgeAction.SPRINT_ACTIVE:
610
- if not payload.team_id:
611
- return text("❌ Informe team_id para consultar o sprint ativo.")
612
- sprint = await self._service.sprint_active(payload.team_id)
594
+ sprint = await self._service.sprint_active()
613
595
  if not sprint:
614
- return text("⏳ Nenhum sprint ativo no momento.")
615
- return text(_format_sprint(sprint, header="⏳ Sprint ativo"))
596
+ return text("⏳ No active sprint at the moment.")
597
+ return text(_format_sprint(sprint, header="⏳ Active sprint"))
616
598
 
617
599
  if action is KnowledgeAction.SPRINT_GET:
618
600
  if not payload.sprint_id:
619
- return text("❌ Informe sprint_id para consultar detalhes.")
601
+ return text("❌ Provide sprint_id to get details.")
620
602
  sprint = await self._service.sprint_get(payload.sprint_id)
621
- return text(_format_sprint(sprint, header="🏃 Detalhes do sprint"))
603
+ return text(_format_sprint(sprint, header="🏃 Sprint details"))
622
604
 
623
605
  if action is KnowledgeAction.SPRINT_WORK_ITEMS:
624
606
  if not payload.sprint_id:
625
- return text("❌ Informe sprint_id para listar os itens.")
607
+ return text("❌ Provide sprint_id to list items.")
626
608
  items = await self._service.sprint_work_items(payload.sprint_id)
627
609
  if not items:
628
- return text("🏃 Nenhum item vinculado ao sprint informado.")
610
+ return text("🏃 No items linked to the specified sprint.")
629
611
  body = "\n\n".join(_format_work(item) for item in items)
630
- return text(f"🏃 **Itens do sprint ({len(items)}):**\n\n{body}")
612
+ return text(f"🏃 **Sprint items ({len(items)}):**\n\n{body}")
631
613
 
632
614
  return text(
633
- "❌ Ação de sprint não suportada.\n\nEscolha um dos valores:\n"
615
+ "❌ Unsupported sprint action.\n\nChoose one of the values:\n"
634
616
  + "\n".join(
635
617
  f"- `{value}`"
636
618
  for value in KnowledgeAction.choices()
@@ -645,7 +627,7 @@ class KnowledgeTool(Tool):
645
627
  action = payload.action
646
628
  if action is KnowledgeAction.MODE_CREATE:
647
629
  if not payload.mode_name:
648
- return text("❌ Informe mode_name para criar o modo.")
630
+ return text("❌ Provide mode_name to create the mode.")
649
631
  mode = await self._service.mode_create(
650
632
  {
651
633
  "name": payload.mode_name,
@@ -655,7 +637,7 @@ class KnowledgeTool(Tool):
655
637
  "metadata": payload.mode_metadata,
656
638
  }
657
639
  )
658
- return text(_format_mode(mode, header="✅ Modo criado"))
640
+ return text(_format_mode(mode, header="✅ Mode created"))
659
641
 
660
642
  if action is KnowledgeAction.MODE_LIST:
661
643
  modes = await self._service.mode_list(
@@ -664,23 +646,23 @@ class KnowledgeTool(Tool):
664
646
  return_metadata=payload.return_metadata,
665
647
  )
666
648
  if not modes:
667
- return text("🎭 Nenhum modo encontrado.")
649
+ return text("🎭 No modes found.")
668
650
  body = "\n\n".join(_format_mode(mode) for mode in modes)
669
651
  return text(f"🎭 **Modes ({len(modes)}):**\n\n{body}")
670
652
 
671
653
  if action is KnowledgeAction.MODE_GET:
672
654
  if not payload.mode_id:
673
- return text("❌ Informe mode_id para consultar detalhes.")
655
+ return text("❌ Provide mode_id to get details.")
674
656
  mode = await self._service.mode_get(
675
657
  payload.mode_id,
676
658
  return_description=payload.return_description,
677
659
  return_metadata=payload.return_metadata,
678
660
  )
679
- return text(_format_mode(mode, header="🎭 Detalhes do modo"))
661
+ return text(_format_mode(mode, header="🎭 Mode details"))
680
662
 
681
663
  if action is KnowledgeAction.MODE_UPDATE:
682
664
  if not payload.mode_id:
683
- return text("❌ Informe mode_id para atualizar.")
665
+ return text("❌ Provide mode_id to update.")
684
666
  mode = await self._service.mode_update(
685
667
  payload.mode_id,
686
668
  {
@@ -691,53 +673,53 @@ class KnowledgeTool(Tool):
691
673
  "metadata": payload.mode_metadata,
692
674
  },
693
675
  )
694
- return text(_format_mode(mode, header="✅ Modo atualizado"))
676
+ return text(_format_mode(mode, header="✅ Mode updated"))
695
677
 
696
678
  if action is KnowledgeAction.MODE_DELETE:
697
679
  if not payload.mode_id:
698
- return text("❌ Informe mode_id para remover.")
680
+ return text("❌ Provide mode_id to remove.")
699
681
  await self._service.mode_delete(payload.mode_id)
700
- return text(f"🗑️ Modo {payload.mode_id} removido.")
682
+ return text(f"🗑️ Mode {payload.mode_id} removed.")
701
683
 
702
684
  if action is KnowledgeAction.MODE_RULE_ADD:
703
685
  if not payload.mode_id or not payload.rule_id:
704
- return text("❌ Informe mode_id e rule_id para associar.")
686
+ return text("❌ Provide mode_id and rule_id to associate.")
705
687
  link = await self._service.mode_rule_add(payload.mode_id, payload.rule_id)
706
688
  return text(
707
689
  "\n".join(
708
690
  [
709
- "🔗 **Regra associada ao modo!**",
710
- f"Modo: {link.get('modeId', payload.mode_id)}",
711
- f"Regra: {link.get('ruleId', payload.rule_id)}",
691
+ "🔗 **Rule associated with mode!**",
692
+ f"Mode: {link.get('modeId', payload.mode_id)}",
693
+ f"Rule: {link.get('ruleId', payload.rule_id)}",
712
694
  ]
713
695
  )
714
696
  )
715
697
 
716
698
  if action is KnowledgeAction.MODE_RULE_REMOVE:
717
699
  if not payload.mode_id or not payload.rule_id:
718
- return text("❌ Informe mode_id e rule_id para remover a associação.")
700
+ return text("❌ Provide mode_id and rule_id to remove the association.")
719
701
  await self._service.mode_rule_remove(payload.mode_id, payload.rule_id)
720
- return text("🔗 Associação removida.")
702
+ return text("🔗 Association removed.")
721
703
 
722
704
  if action is KnowledgeAction.MODE_RULES:
723
705
  if payload.mode_id:
724
706
  rules = await self._service.mode_rules(payload.mode_id)
725
- context_label = f"modo {payload.mode_id}"
707
+ context_label = f"mode {payload.mode_id}"
726
708
  elif payload.rule_id:
727
709
  rules = await self._service.mode_rules_for_rule(payload.rule_id)
728
- context_label = f"regra {payload.rule_id}"
710
+ context_label = f"rule {payload.rule_id}"
729
711
  else:
730
- return text("❌ Informe mode_id ou rule_id para listar associações.")
712
+ return text("❌ Provide mode_id or rule_id to list associations.")
731
713
  if not rules:
732
- return text("🔗 Nenhuma associação encontrada.")
714
+ return text("🔗 No associations found.")
733
715
  body = "\n".join(
734
- f"- {item.get('name', 'Sem nome')} (ID: {item.get('id')})"
716
+ f"- {item.get('name', 'Unnamed')} (ID: {item.get('id')})"
735
717
  for item in rules
736
718
  )
737
- return text(f"🔗 **Associações para {context_label}:**\n{body}")
719
+ return text(f"🔗 **Associations for {context_label}:**\n{body}")
738
720
 
739
721
  return text(
740
- "❌ Ação de modo não suportada.\n\nEscolha um dos valores:\n"
722
+ "❌ Unsupported mode action.\n\nChoose one of the values:\n"
741
723
  + "\n".join(
742
724
  f"- `{value}`"
743
725
  for value in KnowledgeAction.choices()
@@ -752,7 +734,7 @@ class KnowledgeTool(Tool):
752
734
  action = payload.action
753
735
  if action is KnowledgeAction.RULE_CREATE:
754
736
  if not payload.rule_name or not payload.rule_content:
755
- return text("❌ Informe rule_name e rule_content.")
737
+ return text("❌ Provide rule_name and rule_content.")
756
738
  rule = await self._service.rule_create(
757
739
  {
758
740
  "name": payload.rule_name,
@@ -762,7 +744,7 @@ class KnowledgeTool(Tool):
762
744
  "metadata": payload.rule_metadata,
763
745
  }
764
746
  )
765
- return text(_format_rule(rule, header="✅ Regra criada"))
747
+ return text(_format_rule(rule, header="✅ Rule created"))
766
748
 
767
749
  if action is KnowledgeAction.RULE_LIST:
768
750
  rules = await self._service.rule_list(
@@ -771,24 +753,24 @@ class KnowledgeTool(Tool):
771
753
  return_modes=payload.return_metadata,
772
754
  )
773
755
  if not rules:
774
- return text("📋 Nenhuma regra encontrada.")
756
+ return text("📋 No rules found.")
775
757
  body = "\n\n".join(_format_rule(rule) for rule in rules)
776
- return text(f"📋 **Regras ({len(rules)}):**\n\n{body}")
758
+ return text(f"📋 **Rules ({len(rules)}):**\n\n{body}")
777
759
 
778
760
  if action is KnowledgeAction.RULE_GET:
779
761
  if not payload.rule_id:
780
- return text("❌ Informe rule_id para consultar detalhes.")
762
+ return text("❌ Provide rule_id to get details.")
781
763
  rule = await self._service.rule_get(
782
764
  payload.rule_id,
783
765
  return_description=payload.return_description,
784
766
  return_metadata=payload.return_metadata,
785
767
  return_modes=payload.return_metadata,
786
768
  )
787
- return text(_format_rule(rule, header="📋 Detalhes da regra"))
769
+ return text(_format_rule(rule, header="📋 Rule details"))
788
770
 
789
771
  if action is KnowledgeAction.RULE_UPDATE:
790
772
  if not payload.rule_id:
791
- return text("❌ Informe rule_id para atualizar.")
773
+ return text("❌ Provide rule_id to update.")
792
774
  rule = await self._service.rule_update(
793
775
  payload.rule_id,
794
776
  {
@@ -799,16 +781,16 @@ class KnowledgeTool(Tool):
799
781
  "metadata": payload.rule_metadata,
800
782
  },
801
783
  )
802
- return text(_format_rule(rule, header="✅ Regra atualizada"))
784
+ return text(_format_rule(rule, header="✅ Rule updated"))
803
785
 
804
786
  if action is KnowledgeAction.RULE_DELETE:
805
787
  if not payload.rule_id:
806
- return text("❌ Informe rule_id para remover.")
788
+ return text("❌ Provide rule_id to remove.")
807
789
  await self._service.rule_delete(payload.rule_id)
808
- return text(f"🗑️ Regra {payload.rule_id} removida.")
790
+ return text(f"🗑️ Rule {payload.rule_id} removed.")
809
791
 
810
792
  return text(
811
- "❌ Ação de regra não suportada.\n\nEscolha um dos valores:\n"
793
+ "❌ Unsupported rule action.\n\nChoose one of the values:\n"
812
794
  + "\n".join(
813
795
  f"- `{value}`"
814
796
  for value in KnowledgeAction.choices()
@@ -823,11 +805,19 @@ class KnowledgeTool(Tool):
823
805
  action = payload.action
824
806
  if action is KnowledgeAction.DOC_CREATE:
825
807
  if not payload.doc_title:
826
- return text("❌ Informe doc_title para criar a documentação.")
808
+ return text("❌ Provide doc_title to create the documentation.")
827
809
  if payload.doc_type and payload.doc_type not in _ALLOWED_DOC_TYPES:
828
810
  allowed = ", ".join(sorted(_ALLOWED_DOC_TYPES))
829
811
  return text(
830
- "❌ doc_type inválido. Use um dos valores suportados: " + allowed
812
+ "❌ Invalid doc_type. Use one of the supported values: " + allowed
813
+ )
814
+ # Emoji is required for page, api_doc, guide (not for folder)
815
+ doc_type = sanitize_null(payload.doc_type) or "page"
816
+ emoji = sanitize_null(payload.doc_emoji or payload.doc_emote)
817
+ if doc_type != "folder" and not emoji:
818
+ return text(
819
+ "❌ Provide doc_emoji to create documentation. "
820
+ "Emoji is required for page, api_doc, and guide types."
831
821
  )
832
822
  doc = await self._service.doc_create(
833
823
  {
@@ -838,7 +828,6 @@ class KnowledgeTool(Tool):
838
828
  "doc_type": sanitize_null(payload.doc_type),
839
829
  "language": sanitize_null(payload.doc_language),
840
830
  "parent_id": sanitize_null(payload.doc_parent_id),
841
- "team_id": sanitize_null(payload.doc_team_id or payload.team_id),
842
831
  "owner_user_id": sanitize_null(payload.doc_owner_id),
843
832
  "reviewer_user_id": sanitize_null(payload.doc_reviewer_id),
844
833
  "version": sanitize_null(payload.doc_version),
@@ -849,7 +838,7 @@ class KnowledgeTool(Tool):
849
838
  "is_public": payload.doc_is_public,
850
839
  }
851
840
  )
852
- return text(_format_doc(doc, header="✅ Documentação criada"))
841
+ return text(_format_doc(doc, header="✅ Documentation created"))
853
842
 
854
843
  if action is KnowledgeAction.DOC_LIST:
855
844
  docs = await self._service.doc_list(
@@ -858,26 +847,26 @@ class KnowledgeTool(Tool):
858
847
  returnContent=payload.return_content,
859
848
  )
860
849
  if not docs:
861
- return text("📄 Nenhuma documentação encontrada.")
850
+ return text("📄 No documentation found.")
862
851
  body = "\n\n".join(_format_doc(doc) for doc in docs)
863
- return text(f"📄 **Documentos ({len(docs)}):**\n\n{body}")
852
+ return text(f"📄 **Documents ({len(docs)}):**\n\n{body}")
864
853
 
865
854
  if action is KnowledgeAction.DOC_GET:
866
855
  if not payload.id:
867
- return text("❌ Informe o ID da documentação.")
856
+ return text("❌ Provide the documentation ID.")
868
857
  doc = await self._service.doc_get(
869
858
  payload.id,
870
859
  returnContent=payload.return_content,
871
860
  )
872
- return text(_format_doc(doc, header="📄 Detalhes da documentação"))
861
+ return text(_format_doc(doc, header="📄 Documentation details"))
873
862
 
874
863
  if action is KnowledgeAction.DOC_UPDATE:
875
864
  if not payload.id:
876
- return text("❌ Informe o ID da documentação.")
865
+ return text("❌ Provide the documentation ID.")
877
866
  if payload.doc_type and payload.doc_type not in _ALLOWED_DOC_TYPES:
878
867
  allowed = ", ".join(sorted(_ALLOWED_DOC_TYPES))
879
868
  return text(
880
- "❌ doc_type inválido. Use um dos valores suportados: " + allowed
869
+ "❌ Invalid doc_type. Use one of the supported values: " + allowed
881
870
  )
882
871
  doc = await self._service.doc_update(
883
872
  payload.id,
@@ -889,7 +878,6 @@ class KnowledgeTool(Tool):
889
878
  "doc_type": sanitize_null(payload.doc_type),
890
879
  "language": sanitize_null(payload.doc_language),
891
880
  "parent_id": sanitize_null(payload.doc_parent_id),
892
- "team_id": sanitize_null(payload.doc_team_id or payload.team_id),
893
881
  "owner_user_id": sanitize_null(payload.doc_owner_id),
894
882
  "reviewer_user_id": sanitize_null(payload.doc_reviewer_id),
895
883
  "version": sanitize_null(payload.doc_version),
@@ -900,115 +888,99 @@ class KnowledgeTool(Tool):
900
888
  "is_public": payload.doc_is_public,
901
889
  },
902
890
  )
903
- return text(_format_doc(doc, header="✅ Documentação atualizada"))
891
+ return text(_format_doc(doc, header="✅ Documentation updated"))
904
892
 
905
893
  if action is KnowledgeAction.DOC_DELETE:
906
894
  if not payload.id:
907
- return text("❌ Informe o ID da documentação.")
895
+ return text("❌ Provide the documentation ID.")
908
896
  await self._service.doc_delete(payload.id)
909
- return text(f"🗑️ Documentação {payload.id} removida.")
897
+ return text(f"🗑️ Documentation {payload.id} removed.")
910
898
 
911
899
  if action is KnowledgeAction.DOC_SEARCH:
912
900
  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:
915
- return text("❌ Informe query e team_id para buscar documentação.")
901
+ if not query:
902
+ return text("❌ Provide query to search documentation.")
916
903
  docs = await self._service.doc_search(
917
904
  query=query,
918
- team_id=team_id,
919
905
  limit=payload.limit,
920
906
  )
921
907
  if not docs:
922
- return text(
923
- "🔍 Nenhum documento encontrado para os filtros informados."
924
- )
908
+ return text("🔍 No documents found for the specified filters.")
925
909
  body = "\n\n".join(_format_doc(doc) for doc in docs)
926
- return text(f"🔍 **Resultados ({len(docs)}):**\n\n{body}")
910
+ return text(f"🔍 **Results ({len(docs)}):**\n\n{body}")
927
911
 
928
912
  if action is KnowledgeAction.DOC_ROOTS:
929
- team_id = sanitize_null(payload.doc_team_id or payload.team_id)
930
- if not team_id:
931
- return text("❌ Informe team_id para listar raízes.")
932
- docs = await self._service.doc_roots(team_id=team_id)
913
+ docs = await self._service.doc_roots()
933
914
  if not docs:
934
- return text("📚 Nenhuma raiz encontrada.")
915
+ return text("📚 No roots found.")
935
916
  body = "\n".join(
936
- f"- {doc.get('title', 'Sem título')} (ID: {doc.get('id')})"
917
+ f"- {doc.get('title', 'Untitled')} (ID: {doc.get('id')})"
937
918
  for doc in docs
938
919
  )
939
- return text(f"📚 **Raízes de documentação:**\n{body}")
920
+ return text(f"📚 **Documentation roots:**\n{body}")
940
921
 
941
922
  if action is KnowledgeAction.DOC_RECENT:
942
- team_id = sanitize_null(payload.doc_team_id or payload.team_id)
943
- if not team_id:
944
- return text("❌ Informe team_id para listar documentos recentes.")
945
923
  docs = await self._service.doc_recent(
946
- team_id=team_id,
947
924
  limit=payload.limit,
948
925
  )
949
926
  if not docs:
950
- return text("🕒 Nenhuma documentação recente encontrada.")
927
+ return text("🕒 No recent documentation found.")
951
928
  body = "\n\n".join(_format_doc(doc) for doc in docs)
952
- return text(f"🕒 **Documentos recentes ({len(docs)}):**\n\n{body}")
929
+ return text(f"🕒 **Recent documents ({len(docs)}):**\n\n{body}")
953
930
 
954
931
  if action is KnowledgeAction.DOC_ANALYTICS:
955
- team_id = sanitize_null(payload.doc_team_id or payload.team_id)
956
- if not team_id:
957
- return text("❌ Informe team_id para obter analytics.")
958
- analytics = await self._service.doc_analytics(team_id=team_id)
959
- lines = ["📊 **Analytics de Documentação**"]
932
+ analytics = await self._service.doc_analytics()
933
+ lines = ["📊 **Documentation Analytics**"]
960
934
  for key, value in analytics.items():
961
935
  lines.append(f"- {key}: {value}")
962
936
  return text("\n".join(lines))
963
937
 
964
938
  if action is KnowledgeAction.DOC_CHILDREN:
965
939
  if not payload.id:
966
- return text("❌ Informe o ID da documentação.")
940
+ return text("❌ Provide the documentation ID.")
967
941
  docs = await self._service.doc_children(payload.id)
968
942
  if not docs:
969
- return text("📄 Nenhum filho cadastrado para o documento informado.")
943
+ return text("📄 No children registered for the specified document.")
970
944
  body = "\n".join(
971
- f"- {doc.get('title', 'Sem título')} (ID: {doc.get('id')})"
945
+ f"- {doc.get('title', 'Untitled')} (ID: {doc.get('id')})"
972
946
  for doc in docs
973
947
  )
974
- return text(f"📄 **Filhos:**\n{body}")
948
+ return text(f"📄 **Children:**\n{body}")
975
949
 
976
950
  if action is KnowledgeAction.DOC_TREE:
977
951
  if not payload.id:
978
- return text("❌ Informe o ID da documentação.")
952
+ return text("❌ Provide the documentation ID.")
979
953
  tree = await self._service.doc_tree(payload.id)
980
- return text(f"🌳 **Árvore de documentação para {payload.id}:**\n{tree}")
954
+ return text(f"🌳 **Documentation tree for {payload.id}:**\n{tree}")
981
955
 
982
956
  if action is KnowledgeAction.DOC_FULL_TREE:
983
957
  tree = await self._service.doc_full_tree()
984
- return text(f"🌳 **Árvore completa de documentação:**\n{tree}")
958
+ return text(f"🌳 **Full documentation tree:**\n{tree}")
985
959
 
986
960
  if action is KnowledgeAction.DOC_MOVE:
987
961
  if not payload.id:
988
- return text("❌ Informe o ID da documentação.")
962
+ return text("❌ Provide the documentation ID.")
989
963
  if payload.doc_parent_id is None and payload.doc_position is None:
990
- return text(
991
- "❌ Informe doc_parent_id, doc_position ou ambos para mover."
992
- )
964
+ return text("❌ Provide doc_parent_id, doc_position or both to move.")
993
965
  move_payload = {
994
966
  "new_parent_id": payload.doc_parent_id,
995
967
  "new_position": payload.doc_position,
996
968
  }
997
969
  doc = await self._service.doc_move(payload.id, move_payload)
998
- return text(_format_doc(doc, header="📦 Documentação movida"))
970
+ return text(_format_doc(doc, header="📦 Documentation moved"))
999
971
 
1000
972
  if action is KnowledgeAction.DOC_PUBLISH:
1001
973
  if not payload.id:
1002
- return text("❌ Informe o ID da documentação.")
974
+ return text("❌ Provide the documentation ID.")
1003
975
  result = await self._service.doc_publish(payload.id)
1004
- return text(f"🗞️ Documento publicado: {result}")
976
+ return text(f"🗞️ Document published: {result}")
1005
977
 
1006
978
  if action is KnowledgeAction.DOC_VERSION:
1007
979
  if not payload.id:
1008
- return text("❌ Informe o ID da documentação.")
980
+ return text("❌ Provide the documentation ID.")
1009
981
  if not payload.doc_version:
1010
982
  return text(
1011
- "❌ Informe doc_version com o número/identificador da versão."
983
+ "❌ Provide doc_version with the version number/identifier."
1012
984
  )
1013
985
  version_payload = {
1014
986
  "title": payload.doc_title or f"Version {payload.doc_version}",
@@ -1016,24 +988,23 @@ class KnowledgeTool(Tool):
1016
988
  "content": payload.doc_content,
1017
989
  }
1018
990
  doc = await self._service.doc_version(payload.id, version_payload)
1019
- return text(_format_doc(doc, header="🗞️ Nova versão criada"))
991
+ return text(_format_doc(doc, header="🗞️ New version created"))
1020
992
 
1021
993
  if action is KnowledgeAction.DOC_DUPLICATE:
1022
994
  if not payload.id:
1023
- return text("❌ Informe o ID da documentação.")
995
+ return text("❌ Provide the documentation ID.")
1024
996
  if not payload.doc_title:
1025
- return text("❌ Informe doc_title para nomear a cópia.")
997
+ return text("❌ Provide doc_title to name the copy.")
1026
998
  doc = await self._service.doc_duplicate(
1027
999
  payload.id,
1028
1000
  {
1029
1001
  "title": payload.doc_title,
1030
- "team_id": payload.doc_team_id or payload.team_id,
1031
1002
  },
1032
1003
  )
1033
- return text(_format_doc(doc, header="🗂️ Documento duplicado"))
1004
+ return text(_format_doc(doc, header="🗂️ Document duplicated"))
1034
1005
 
1035
1006
  return text(
1036
- "❌ Ação de documentação não suportada.\n\nEscolha um dos valores:\n"
1007
+ "❌ Unsupported documentation action.\n\nChoose one of the values:\n"
1037
1008
  + "\n".join(
1038
1009
  f"- `{value}`"
1039
1010
  for value in KnowledgeAction.choices()
@@ -1043,7 +1014,7 @@ class KnowledgeTool(Tool):
1043
1014
 
1044
1015
  async def _handle_help(self):
1045
1016
  return text(
1046
- "📚 **Ações disponíveis para knowledge**\n\n"
1017
+ "📚 **Available actions for knowledge**\n\n"
1047
1018
  + KnowledgeAction.formatted_help()
1048
1019
  )
1049
1020
 
@@ -1055,13 +1026,13 @@ def _format_work(item: Dict[str, Any], *, header: Optional[str] = None) -> str:
1055
1026
  lines.append("")
1056
1027
 
1057
1028
  # Extract title
1058
- title = item.get("title") or item.get("name") or "Sem título"
1029
+ title = item.get("title") or item.get("name") or "Untitled"
1059
1030
 
1060
1031
  # Extract status
1061
- status = item.get("status") or item.get("state") or "desconhecido"
1032
+ status = item.get("status") or item.get("state") or "unknown"
1062
1033
 
1063
1034
  # Extract priority
1064
- priority = item.get("priority") or item.get("priority_level") or "indefinido"
1035
+ priority = item.get("priority") or item.get("priority_level") or "undefined"
1065
1036
 
1066
1037
  # Extract ID
1067
1038
  item_id = item.get("id", "N/A")
@@ -1080,13 +1051,13 @@ def _format_work(item: Dict[str, Any], *, header: Optional[str] = None) -> str:
1080
1051
  f"🎯 **{title}**",
1081
1052
  f"ID: {item_id}",
1082
1053
  f"Status: {status}",
1083
- f"Prioridade: {priority}",
1084
- f"Responsável: {assignee}",
1054
+ f"Priority: {priority}",
1055
+ f"Assignee: {assignee}",
1085
1056
  ]
1086
1057
  )
1087
1058
  if item.get("due_date") or item.get("dueDate"):
1088
1059
  lines.append(
1089
- f"Vencimento: {_format_date(item.get('due_date') or item.get('dueDate'))}"
1060
+ f"Due date: {_format_date(item.get('due_date') or item.get('dueDate'))}"
1090
1061
  )
1091
1062
  if item.get("tags"):
1092
1063
  tags = item.get("tags", [])
@@ -1102,10 +1073,10 @@ def _format_board(board: Dict[str, Any], header: Optional[str] = None) -> str:
1102
1073
  lines.append("")
1103
1074
  lines.extend(
1104
1075
  [
1105
- f"🗂️ **{board.get('name', 'Sem nome')}**",
1076
+ f"🗂️ **{board.get('name', 'Unnamed')}**",
1106
1077
  f"ID: {board.get('id', 'N/A')}",
1107
- f"Time: {board.get('team_id', 'N/A')}",
1108
- f"Colunas: {len(board.get('columns', []))}",
1078
+ f"Team: {board.get('team_id', 'N/A')}",
1079
+ f"Columns: {len(board.get('columns', []))}",
1109
1080
  ]
1110
1081
  )
1111
1082
  return "\n".join(lines)
@@ -1118,19 +1089,19 @@ def _format_sprint(sprint: Dict[str, Any], header: Optional[str] = None) -> str:
1118
1089
  lines.append("")
1119
1090
  lines.extend(
1120
1091
  [
1121
- f"🏃 **{sprint.get('name', 'Sem nome')}**",
1092
+ f"🏃 **{sprint.get('name', 'Unnamed')}**",
1122
1093
  f"ID: {sprint.get('id', 'N/A')}",
1123
1094
  f"Status: {sprint.get('status', 'N/A')}",
1124
- f"Time: {sprint.get('team_id', 'N/A')}",
1095
+ f"Team: {sprint.get('team_id', 'N/A')}",
1125
1096
  ]
1126
1097
  )
1127
1098
  if sprint.get("start_date") or sprint.get("startDate"):
1128
1099
  lines.append(
1129
- f"Início: {_format_date(sprint.get('start_date') or sprint.get('startDate'))}"
1100
+ f"Start: {_format_date(sprint.get('start_date') or sprint.get('startDate'))}"
1130
1101
  )
1131
1102
  if sprint.get("end_date") or sprint.get("endDate"):
1132
1103
  lines.append(
1133
- f"Fim: {_format_date(sprint.get('end_date') or sprint.get('endDate'))}"
1104
+ f"End: {_format_date(sprint.get('end_date') or sprint.get('endDate'))}"
1134
1105
  )
1135
1106
  return "\n".join(lines)
1136
1107
 
@@ -1142,13 +1113,13 @@ def _format_mode(mode: Dict[str, Any], header: Optional[str] = None) -> str:
1142
1113
  lines.append("")
1143
1114
  lines.extend(
1144
1115
  [
1145
- f"🎭 **{mode.get('name', 'Sem nome')}**",
1116
+ f"🎭 **{mode.get('name', 'Unnamed')}**",
1146
1117
  f"ID: {mode.get('id', 'N/A')}",
1147
- f"Padrão: {mode.get('is_default', False)}",
1118
+ f"Default: {mode.get('is_default', False)}",
1148
1119
  ]
1149
1120
  )
1150
1121
  if mode.get("description"):
1151
- lines.append(f"Descrição: {mode['description']}")
1122
+ lines.append(f"Description: {mode['description']}")
1152
1123
  return "\n".join(lines)
1153
1124
 
1154
1125
 
@@ -1159,13 +1130,13 @@ def _format_rule(rule: Dict[str, Any], header: Optional[str] = None) -> str:
1159
1130
  lines.append("")
1160
1131
  lines.extend(
1161
1132
  [
1162
- f"📋 **{rule.get('name', 'Sem nome')}**",
1133
+ f"📋 **{rule.get('name', 'Unnamed')}**",
1163
1134
  f"ID: {rule.get('id', 'N/A')}",
1164
- f"Padrão: {rule.get('is_default', False)}",
1135
+ f"Default: {rule.get('is_default', False)}",
1165
1136
  ]
1166
1137
  )
1167
1138
  if rule.get("description"):
1168
- lines.append(f"Descrição: {rule['description']}")
1139
+ lines.append(f"Description: {rule['description']}")
1169
1140
  return "\n".join(lines)
1170
1141
 
1171
1142
 
@@ -1176,15 +1147,15 @@ def _format_doc(doc: Dict[str, Any], header: Optional[str] = None) -> str:
1176
1147
  lines.append("")
1177
1148
  lines.extend(
1178
1149
  [
1179
- f"📄 **{doc.get('title') or doc.get('name', 'Sem título')}**",
1150
+ f"📄 **{doc.get('title') or doc.get('name', 'Untitled')}**",
1180
1151
  f"ID: {doc.get('id', 'N/A')}",
1181
1152
  f"Status: {doc.get('status', 'N/A')}",
1182
- f"Time: {doc.get('team_id', 'N/A')}",
1153
+ f"Team: {doc.get('team_id', 'N/A')}",
1183
1154
  ]
1184
1155
  )
1185
1156
  if doc.get("updated_at") or doc.get("updatedAt"):
1186
1157
  lines.append(
1187
- f"Atualizado em: {_format_date(doc.get('updated_at') or doc.get('updatedAt'))}"
1158
+ f"Updated at: {_format_date(doc.get('updated_at') or doc.get('updatedAt'))}"
1188
1159
  )
1189
1160
  return "\n".join(lines)
1190
1161