codee-agent 0.5.2__tar.gz → 0.5.3__tar.gz

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.
Files changed (81) hide show
  1. {codee_agent-0.5.2 → codee_agent-0.5.3}/PKG-INFO +1 -1
  2. {codee_agent-0.5.2 → codee_agent-0.5.3}/pyproject.toml +1 -1
  3. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/admin.py +3 -4
  4. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/admin_service.py +14 -5
  5. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/test_admin_service.py +72 -17
  6. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_tasks_azure_devops/provider.py +7 -0
  7. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_tasks_azure_devops/test.py +25 -0
  8. {codee_agent-0.5.2 → codee_agent-0.5.3}/LICENSE +0 -0
  9. {codee_agent-0.5.2 → codee_agent-0.5.3}/README.md +0 -0
  10. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/.gitignore +0 -0
  11. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/__init__.py +0 -0
  12. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/admin_api.py +0 -0
  13. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/admin_cli.py +0 -0
  14. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/agent_cli.py +0 -0
  15. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/coding_agents.py +0 -0
  16. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/executor.py +0 -0
  17. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/init_cli.py +0 -0
  18. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/lib/__init__.py +0 -0
  19. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/lib/cron_describe.py +0 -0
  20. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/lib/mcp_config.py +0 -0
  21. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/lib/runs_db.py +0 -0
  22. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/lib/test_mcp_config.py +0 -0
  23. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/lib/test_runs_db.py +0 -0
  24. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/lib/test_trigger_cron_skills.py +0 -0
  25. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/lib/test_trigger_issue_skills.py +0 -0
  26. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/lib/trigger_aws_sqs_skills.py +0 -0
  27. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/lib/trigger_cron_skills.py +0 -0
  28. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/lib/trigger_email_skills.py +0 -0
  29. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/lib/trigger_issue_skills.py +0 -0
  30. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/mail_server.py +0 -0
  31. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/setup_wizard.py +0 -0
  32. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/start_cli.py +0 -0
  33. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/tasks_providers.py +0 -0
  34. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/AGENTS.md +0 -0
  35. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/CLAUDE.md +0 -0
  36. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/skills/aws-sqs-alarm-response/SKILL.md +0 -0
  37. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/skills/cron-research-5xx-errors/SKILL.md +0 -0
  38. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/skills/story-code-reviewer/SKILL.md +0 -0
  39. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/skills/story-developer/SKILL.md +0 -0
  40. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/skills/story-planner/SKILL.md +0 -0
  41. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/skills/story-planner/assets/readme-template.md +0 -0
  42. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/skills/story-qa/SKILL.md +0 -0
  43. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/skills/story-security-reviewer/SKILL.md +0 -0
  44. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/skills/task-code-reviewer/SKILL.md +0 -0
  45. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/skills/task-developer/SKILL.md +0 -0
  46. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/skills/task-qa/SKILL.md +0 -0
  47. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/templates/skills/task-security-reviewer/SKILL.md +0 -0
  48. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/test_admin_api.py +0 -0
  49. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/test_admin_cli.py +0 -0
  50. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/test_agent_cli.py +0 -0
  51. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/test_executor.py +0 -0
  52. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/test_init_cli.py +0 -0
  53. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/test_memory_index.py +0 -0
  54. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/test_setup_wizard.py +0 -0
  55. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/test_start_cli.py +0 -0
  56. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee/workflow_graph.py +0 -0
  57. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_admin/__init__.py +0 -0
  58. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_admin/codee_admin.py +0 -0
  59. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_agent_abstract/__init__.py +0 -0
  60. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_agent_abstract/provider.py +0 -0
  61. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_agent_claude_code/__init__.py +0 -0
  62. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_agent_claude_code/provider.py +0 -0
  63. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_agent_claude_code/test.py +0 -0
  64. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_agent_github_copilot/__init__.py +0 -0
  65. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_agent_github_copilot/provider.py +0 -0
  66. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_agent_github_copilot/test.py +0 -0
  67. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_database/__init__.py +0 -0
  68. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_database/database.py +0 -0
  69. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_database/oauth_tokens.py +0 -0
  70. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_main_context/__init__.py +0 -0
  71. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_main_context/context.py +0 -0
  72. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_main_context/logging.py +0 -0
  73. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_main_context/test_context.py +0 -0
  74. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_main_context/test_logging.py +0 -0
  75. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_tasks_abstract/__init__.py +0 -0
  76. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_tasks_abstract/provider.py +0 -0
  77. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_tasks_azure_devops/__init__.py +0 -0
  78. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_tasks_azure_devops/oauth.py +0 -0
  79. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_tasks_jira/__init__.py +0 -0
  80. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_tasks_jira/provider.py +0 -0
  81. {codee_agent-0.5.2 → codee_agent-0.5.3}/src/codee_tasks_jira/test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codee-agent
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: A virtual co-worker that picks up Jira and Azure DevOps tasks and solves them with coding agents.
5
5
  Keywords: ai,agent,jira,azure-devops,claude-code,github-copilot,automation
6
6
  Author: Denis Kibalko
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codee-agent"
3
- version = "0.5.2"
3
+ version = "0.5.3"
4
4
  description = "A virtual co-worker that picks up Jira and Azure DevOps tasks and solves them with coding agents."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -2215,9 +2215,7 @@ def task_filter_setting() -> rx.Component:
2215
2215
  rows="2", width="100%")),
2216
2216
  hint=rx.text("Optional. Added to every task query as one more AND "
2217
2217
  "condition, on top of the work items above. Leave it "
2218
- "empty to pick up everything they match. The poll does "
2219
- "not filter on an assignee — put one here if you want "
2220
- "one.",
2218
+ "empty to pick up everything they match.",
2221
2219
  color=MUTED, font_size="0.8rem"))
2222
2220
 
2223
2221
 
@@ -2240,7 +2238,8 @@ def tasks_check_row(check: CheckResult) -> rx.Component:
2240
2238
  color=rx.cond(check.status == "waiting", MUTED, TEXT)),
2241
2239
  rx.cond(
2242
2240
  check.message != "",
2243
- rx.text(check.message, color=MUTED, font_size="0.8rem")),
2241
+ rx.text(check.message, color=MUTED, font_size="0.8rem",
2242
+ white_space="pre-wrap", overflow_wrap="anywhere")),
2244
2243
  spacing="1", align="start", width="100%"),
2245
2244
  spacing="2", align="start", width="100%")
2246
2245
 
@@ -159,7 +159,9 @@ def _mcp_check_prompt(server_name: str, steps: list[str]) -> str:
159
159
  '"status": "<the status you left it in>"}\n'
160
160
  "If any step did not complete:\n"
161
161
  '{"ok": false, "error": "<one sentence naming the step that failed '
162
- 'and why>"}'
162
+ 'and why>", "request": "<the complete MCP tool request exactly as '
163
+ 'sent, or null if none was sent>", "response": "<the complete MCP '
164
+ 'response or error exactly as received, or null if none was received>"}'
163
165
  )
164
166
 
165
167
 
@@ -177,11 +179,18 @@ def _mcp_check_result(server_name: str, response: str) -> dict[str, Any]:
177
179
  raise ValueError
178
180
  except ValueError:
179
181
  return _check(MCP_CHECK, False, "The agent did not report a result: "
180
- f"{response.strip()[:300] or 'it said nothing'}")
182
+ f"{response.strip() or 'it said nothing'}")
181
183
  if not payload.get("ok"):
182
- return _check(MCP_CHECK, False,
183
- str(payload.get("error")
184
- or "The agent reported a failure."))
184
+ message = str(payload.get("error")
185
+ or "The agent reported a failure.")
186
+ if "request" in payload or "response" in payload:
187
+ request = json.dumps(payload.get("request"), indent=2,
188
+ ensure_ascii=False)
189
+ response = json.dumps(payload.get("response"), indent=2,
190
+ ensure_ascii=False)
191
+ message += (f"\n\nMCP request:\n{request}"
192
+ f"\n\nMCP response:\n{response}")
193
+ return _check(MCP_CHECK, False, message)
185
194
  task = str(payload.get("task") or "a task").strip()
186
195
  status = str(payload.get("status") or "a closed status").strip()
187
196
  return _check(MCP_CHECK, True,
@@ -911,7 +911,8 @@ class AdminServiceSkillExtraFrontmatterTest(unittest.TestCase):
911
911
 
912
912
  def test_save_writes_the_extra_fields_into_the_frontmatter(self) -> None:
913
913
  with tempfile.TemporaryDirectory() as temporary_directory:
914
- service = self._service(Path(temporary_directory), "name: nightly\n")
914
+ service = self._service(
915
+ Path(temporary_directory), "name: nightly\n")
915
916
 
916
917
  write, _ = self._save(
917
918
  service, "allowed-tools: Bash\ncompatibility: Claude Code\n")
@@ -942,9 +943,11 @@ class AdminServiceSkillExtraFrontmatterTest(unittest.TestCase):
942
943
 
943
944
  def test_invalid_yaml_is_reported_and_nothing_is_written(self) -> None:
944
945
  with tempfile.TemporaryDirectory() as temporary_directory:
945
- service = self._service(Path(temporary_directory), "name: nightly\n")
946
+ service = self._service(
947
+ Path(temporary_directory), "name: nightly\n")
946
948
 
947
- write, (saved, _, message, slug) = self._save(service, "allowed-tools")
949
+ write, (saved, _, message, slug) = self._save(
950
+ service, "allowed-tools")
948
951
 
949
952
  write.assert_not_called()
950
953
  self.assertFalse(saved)
@@ -953,7 +956,8 @@ class AdminServiceSkillExtraFrontmatterTest(unittest.TestCase):
953
956
 
954
957
  def test_a_field_that_has_its_own_control_is_rejected(self) -> None:
955
958
  with tempfile.TemporaryDirectory() as temporary_directory:
956
- service = self._service(Path(temporary_directory), "name: nightly\n")
959
+ service = self._service(
960
+ Path(temporary_directory), "name: nightly\n")
957
961
 
958
962
  write, (saved, _, message, _) = self._save(
959
963
  service, "model: claude-opus-5\n")
@@ -973,7 +977,8 @@ class AdminServiceSkillExtraFrontmatterTest(unittest.TestCase):
973
977
 
974
978
  def test_load_returns_an_empty_string_when_there_are_none(self) -> None:
975
979
  with tempfile.TemporaryDirectory() as temporary_directory:
976
- service = self._service(Path(temporary_directory), "name: nightly\n")
980
+ service = self._service(
981
+ Path(temporary_directory), "name: nightly\n")
977
982
 
978
983
  self.assertEqual(service.load_skill("nightly")["extra"], "")
979
984
 
@@ -991,12 +996,14 @@ class AdminServiceAgentModelsTest(unittest.TestCase):
991
996
  service = self._service(CodingAgent.CLAUDE_CODE)
992
997
 
993
998
  with patch.object(ClaudeCodeAgent, "list_models",
994
- return_value=[AgentModel("claude-opus-5", "Claude Opus 5")]
999
+ return_value=[AgentModel(
1000
+ "claude-opus-5", "Claude Opus 5")]
995
1001
  ) as list_models:
996
1002
  first = service.list_agent_models()
997
1003
  second = service.list_agent_models()
998
1004
 
999
- self.assertEqual(first, [{"id": "claude-opus-5", "name": "Claude Opus 5"}])
1005
+ self.assertEqual(
1006
+ first, [{"id": "claude-opus-5", "name": "Claude Opus 5"}])
1000
1007
  self.assertEqual(second, first)
1001
1008
  list_models.assert_called_once()
1002
1009
 
@@ -1092,10 +1099,12 @@ class AdminServiceRepositoriesTest(unittest.TestCase):
1092
1099
  return result.stdout.strip()
1093
1100
 
1094
1101
  def test_name_is_taken_from_every_url_form(self) -> None:
1095
- self.assertEqual(repository_name("git@github.com:org/codee.git"), "codee")
1102
+ self.assertEqual(repository_name(
1103
+ "git@github.com:org/codee.git"), "codee")
1096
1104
  self.assertEqual(repository_name("ssh://git@github.com/org/codee.git"),
1097
1105
  "codee")
1098
- self.assertEqual(repository_name("https://github.com/org/codee/"), "codee")
1106
+ self.assertEqual(repository_name(
1107
+ "https://github.com/org/codee/"), "codee")
1099
1108
  self.assertEqual(repository_name(" "), "")
1100
1109
 
1101
1110
  def test_add_builds_the_bare_and_worktree_layout(self) -> None:
@@ -1197,7 +1206,8 @@ class AzureDevOpsOAuthTest(unittest.TestCase):
1197
1206
  tasks_provider=TasksProvider.AZURE_DEVOPS,
1198
1207
  credentials={"azure_devops": credentials
1199
1208
  if credentials is not None else self.AZURE_CREDENTIALS})
1200
- service.context = CodeeMainContext(data_dir=data_dir, settings=settings)
1209
+ service.context = CodeeMainContext(
1210
+ data_dir=data_dir, settings=settings)
1201
1211
  save_settings(data_dir, settings)
1202
1212
  return service
1203
1213
 
@@ -1241,14 +1251,16 @@ class AzureDevOpsOAuthTest(unittest.TestCase):
1241
1251
 
1242
1252
  with patch.object(azure_oauth, "exchange_code", return_value=tokens) as exchange, \
1243
1253
  patch.object(azure_oauth, "fetch_account", return_value="dev@acme.com"):
1244
- connected, message = service.complete_azure_authorization("code-1", state)
1254
+ connected, message = service.complete_azure_authorization(
1255
+ "code-1", state)
1245
1256
 
1246
1257
  self.assertTrue(started)
1247
1258
  self.assertTrue(connected)
1248
1259
  self.assertIn("dev@acme.com", message)
1249
1260
  # The exchange must reuse the redirect URI the authorization was issued
1250
1261
  # with — Entra rejects the code otherwise.
1251
- self.assertEqual(exchange.call_args.args[1], service.azure_redirect_uri())
1262
+ self.assertEqual(
1263
+ exchange.call_args.args[1], service.azure_redirect_uri())
1252
1264
  self.assertTrue(service.azure_connection()["connected"])
1253
1265
 
1254
1266
  def test_callback_with_a_forged_state_stores_nothing(self) -> None:
@@ -1274,7 +1286,8 @@ class AzureDevOpsOAuthTest(unittest.TestCase):
1274
1286
  with patch.object(azure_oauth, "exchange_code",
1275
1287
  side_effect=azure_oauth.AzureDevOpsAuthError(
1276
1288
  "AADSTS7000215: Invalid client secret.")):
1277
- connected, message = service.complete_azure_authorization("code-1", state)
1289
+ connected, message = service.complete_azure_authorization(
1290
+ "code-1", state)
1278
1291
 
1279
1292
  self.assertFalse(connected)
1280
1293
  self.assertIn("Invalid client secret", message)
@@ -1312,7 +1325,8 @@ class VerifyTasksConnectionTest(unittest.TestCase):
1312
1325
  service.skills_dir = _write_issue_skill(root)
1313
1326
  service.data_dir = root / ".codee"
1314
1327
  service.data_dir.mkdir()
1315
- settings = Settings(credentials={"jira": {"base_url": "https://stale.test"}})
1328
+ settings = Settings(
1329
+ credentials={"jira": {"base_url": "https://stale.test"}})
1316
1330
  service.context = CodeeMainContext(
1317
1331
  data_dir=service.data_dir, settings=settings)
1318
1332
  save_settings(service.data_dir, settings)
@@ -1463,6 +1477,10 @@ class VerifyTasksMcpCheckTest(unittest.TestCase):
1463
1477
  self.prompt)
1464
1478
  self.assertIn("2. Move that issue to a Done or Cancelled status",
1465
1479
  self.prompt)
1480
+ self.assertIn("complete MCP tool request exactly as sent",
1481
+ self.prompt)
1482
+ self.assertIn("complete MCP response or error exactly as received",
1483
+ self.prompt)
1466
1484
 
1467
1485
  def test_a_fenced_reply_is_still_read(self) -> None:
1468
1486
  with tempfile.TemporaryDirectory() as temporary_directory:
@@ -1484,6 +1502,40 @@ class VerifyTasksMcpCheckTest(unittest.TestCase):
1484
1502
  self.assertEqual(check["message"],
1485
1503
  "No create-issue tool was offered.")
1486
1504
 
1505
+ def test_a_reported_failure_includes_the_full_mcp_exchange(self) -> None:
1506
+ with tempfile.TemporaryDirectory() as temporary_directory:
1507
+ service = self._service(Path(temporary_directory))
1508
+ request = {
1509
+ "tool": "ado-wit_work_item_write",
1510
+ "arguments": {"fields": [{"name": "System.Title",
1511
+ "value": "Codee check"}]},
1512
+ }
1513
+ response = {
1514
+ "error": {"code": "invalid_type",
1515
+ "message": "Input validation failed"}}
1516
+
1517
+ check = self._run(service, json.dumps({
1518
+ "ok": False,
1519
+ "error": "Creating the work item failed.",
1520
+ "request": request,
1521
+ "response": response,
1522
+ }))
1523
+
1524
+ self.assertFalse(check["ok"])
1525
+ self.assertIn("MCP request:\n" + json.dumps(
1526
+ request, indent=2), check["message"])
1527
+ self.assertIn("MCP response:\n" + json.dumps(
1528
+ response, indent=2), check["message"])
1529
+
1530
+ def test_a_non_json_failure_is_not_truncated(self) -> None:
1531
+ with tempfile.TemporaryDirectory() as temporary_directory:
1532
+ service = self._service(Path(temporary_directory))
1533
+ response = "x" * 500
1534
+
1535
+ check = self._run(service, response)
1536
+
1537
+ self.assertTrue(check["message"].endswith(response))
1538
+
1487
1539
  def test_an_answer_that_is_not_the_asked_for_json_fails_the_check(self) -> None:
1488
1540
  # An agent that ignored the format can't be believed about the rest.
1489
1541
  with tempfile.TemporaryDirectory() as temporary_directory:
@@ -1558,7 +1610,8 @@ class SetupTasksMcpTest(unittest.TestCase):
1558
1610
  service.root = root
1559
1611
  service.data_dir = root / ".codee"
1560
1612
  service.data_dir.mkdir()
1561
- settings = Settings(credentials={"jira": {"base_url": "https://stale.test"}})
1613
+ settings = Settings(
1614
+ credentials={"jira": {"base_url": "https://stale.test"}})
1562
1615
  service.context = CodeeMainContext(
1563
1616
  data_dir=service.data_dir, settings=settings)
1564
1617
  save_settings(service.data_dir, settings)
@@ -1572,7 +1625,8 @@ class SetupTasksMcpTest(unittest.TestCase):
1572
1625
  root = Path(temporary_directory)
1573
1626
  service = self._service(root)
1574
1627
 
1575
- done, message = service.setup_tasks_mcp("jira", self.JIRA_CREDENTIALS)
1628
+ done, message = service.setup_tasks_mcp(
1629
+ "jira", self.JIRA_CREDENTIALS)
1576
1630
 
1577
1631
  self.assertTrue(done, message)
1578
1632
  config = self._config(root)
@@ -1670,7 +1724,8 @@ class SetupTasksMcpTest(unittest.TestCase):
1670
1724
  service = self._service(root)
1671
1725
  (root / ".mcp.json").write_text("{not json")
1672
1726
 
1673
- done, message = service.setup_tasks_mcp("jira", self.JIRA_CREDENTIALS)
1727
+ done, message = service.setup_tasks_mcp(
1728
+ "jira", self.JIRA_CREDENTIALS)
1674
1729
 
1675
1730
  self.assertFalse(done)
1676
1731
  self.assertIn("not valid JSON", message)
@@ -166,6 +166,13 @@ class AzureDevOpsTasksProvider(AbstractTasksProvider):
166
166
  return (f"Azure DevOps {self._config.organization_url} "
167
167
  f"(all projects, connected as {account}, types {types}{extra})")
168
168
 
169
+ def verify_connection(self, statuses: list[str]) -> tuple[bool, str]:
170
+ """Pull tasks and include the exact WIQL in a successful check."""
171
+ verified, message = super().verify_connection(statuses)
172
+ if not verified:
173
+ return verified, message
174
+ return verified, f"{message}\n\nWIQL: {self._build_wiql(statuses)}"
175
+
169
176
  def mcp_server(self) -> McpServer | None:
170
177
  """Microsoft's Azure DevOps MCP server, addressed at this organization.
171
178
 
@@ -558,6 +558,31 @@ class TasksProviderTest(unittest.TestCase):
558
558
 
559
559
  self.assertTrue(verified)
560
560
  self.assertIn("11 Fix the thing", message)
561
+ self.assertIn(
562
+ "WIQL: SELECT [System.Id] FROM WorkItems "
563
+ "WHERE [System.WorkItemType] IN ('User Story', 'Task') "
564
+ "AND [System.State] IN ('Ready') "
565
+ "ORDER BY [Microsoft.VSTS.Common.Priority] ASC, "
566
+ "[System.CreatedDate] ASC",
567
+ message)
568
+
569
+ def test_an_empty_check_prints_the_custom_filter_in_the_raw_wiql(self) -> None:
570
+ self._connect()
571
+ settings = _settings(organization_url="https://dev.azure.com/acme",
572
+ client_id="client-1", client_secret="secret-1")
573
+ settings.task_filters = {
574
+ "azure_devops": "[Custom.Assistant] = 'Codee Agent User Account'"}
575
+ provider = AzureDevOpsTasksProvider(settings, self.context)
576
+
577
+ with patch("codee_tasks_azure_devops.provider.requests.post",
578
+ return_value=_response({"workItems": []})):
579
+ verified, message = provider.verify_connection(["Ready"])
580
+
581
+ self.assertTrue(verified)
582
+ self.assertIn("No tasks are waiting for it right now.", message)
583
+ self.assertIn(
584
+ "AND ([Custom.Assistant] = 'Codee Agent User Account') ORDER BY",
585
+ message)
561
586
 
562
587
  def test_a_rejected_query_reports_what_azure_devops_said(self) -> None:
563
588
  self._connect()
File without changes
File without changes