codee-agent 0.5.1__tar.gz → 0.5.2__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.1 → codee_agent-0.5.2}/PKG-INFO +1 -1
  2. {codee_agent-0.5.1 → codee_agent-0.5.2}/pyproject.toml +1 -1
  3. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/admin.py +3 -1
  4. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_tasks_abstract/provider.py +1 -1
  5. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_tasks_azure_devops/provider.py +42 -28
  6. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_tasks_azure_devops/test.py +11 -3
  7. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_tasks_jira/provider.py +1 -1
  8. {codee_agent-0.5.1 → codee_agent-0.5.2}/LICENSE +0 -0
  9. {codee_agent-0.5.1 → codee_agent-0.5.2}/README.md +0 -0
  10. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/.gitignore +0 -0
  11. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/__init__.py +0 -0
  12. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/admin_api.py +0 -0
  13. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/admin_cli.py +0 -0
  14. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/admin_service.py +0 -0
  15. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/agent_cli.py +0 -0
  16. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/coding_agents.py +0 -0
  17. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/executor.py +0 -0
  18. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/init_cli.py +0 -0
  19. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/lib/__init__.py +0 -0
  20. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/lib/cron_describe.py +0 -0
  21. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/lib/mcp_config.py +0 -0
  22. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/lib/runs_db.py +0 -0
  23. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/lib/test_mcp_config.py +0 -0
  24. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/lib/test_runs_db.py +0 -0
  25. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/lib/test_trigger_cron_skills.py +0 -0
  26. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/lib/test_trigger_issue_skills.py +0 -0
  27. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/lib/trigger_aws_sqs_skills.py +0 -0
  28. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/lib/trigger_cron_skills.py +0 -0
  29. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/lib/trigger_email_skills.py +0 -0
  30. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/lib/trigger_issue_skills.py +0 -0
  31. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/mail_server.py +0 -0
  32. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/setup_wizard.py +0 -0
  33. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/start_cli.py +0 -0
  34. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/tasks_providers.py +0 -0
  35. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/AGENTS.md +0 -0
  36. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/CLAUDE.md +0 -0
  37. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/skills/aws-sqs-alarm-response/SKILL.md +0 -0
  38. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/skills/cron-research-5xx-errors/SKILL.md +0 -0
  39. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/skills/story-code-reviewer/SKILL.md +0 -0
  40. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/skills/story-developer/SKILL.md +0 -0
  41. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/skills/story-planner/SKILL.md +0 -0
  42. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/skills/story-planner/assets/readme-template.md +0 -0
  43. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/skills/story-qa/SKILL.md +0 -0
  44. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/skills/story-security-reviewer/SKILL.md +0 -0
  45. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/skills/task-code-reviewer/SKILL.md +0 -0
  46. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/skills/task-developer/SKILL.md +0 -0
  47. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/skills/task-qa/SKILL.md +0 -0
  48. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/templates/skills/task-security-reviewer/SKILL.md +0 -0
  49. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/test_admin_api.py +0 -0
  50. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/test_admin_cli.py +0 -0
  51. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/test_admin_service.py +0 -0
  52. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/test_agent_cli.py +0 -0
  53. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/test_executor.py +0 -0
  54. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/test_init_cli.py +0 -0
  55. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/test_memory_index.py +0 -0
  56. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/test_setup_wizard.py +0 -0
  57. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/test_start_cli.py +0 -0
  58. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee/workflow_graph.py +0 -0
  59. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_admin/__init__.py +0 -0
  60. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_admin/codee_admin.py +0 -0
  61. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_agent_abstract/__init__.py +0 -0
  62. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_agent_abstract/provider.py +0 -0
  63. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_agent_claude_code/__init__.py +0 -0
  64. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_agent_claude_code/provider.py +0 -0
  65. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_agent_claude_code/test.py +0 -0
  66. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_agent_github_copilot/__init__.py +0 -0
  67. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_agent_github_copilot/provider.py +0 -0
  68. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_agent_github_copilot/test.py +0 -0
  69. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_database/__init__.py +0 -0
  70. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_database/database.py +0 -0
  71. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_database/oauth_tokens.py +0 -0
  72. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_main_context/__init__.py +0 -0
  73. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_main_context/context.py +0 -0
  74. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_main_context/logging.py +0 -0
  75. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_main_context/test_context.py +0 -0
  76. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_main_context/test_logging.py +0 -0
  77. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_tasks_abstract/__init__.py +0 -0
  78. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_tasks_azure_devops/__init__.py +0 -0
  79. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_tasks_azure_devops/oauth.py +0 -0
  80. {codee_agent-0.5.1 → codee_agent-0.5.2}/src/codee_tasks_jira/__init__.py +0 -0
  81. {codee_agent-0.5.1 → codee_agent-0.5.2}/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.1
3
+ Version: 0.5.2
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.1"
3
+ version = "0.5.2"
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,7 +2215,9 @@ 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.",
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.",
2219
2221
  color=MUTED, font_size="0.8rem"))
2220
2222
 
2221
2223
 
@@ -146,7 +146,7 @@ class AbstractTasksProvider(ABC):
146
146
  """
147
147
  if not tasks:
148
148
  return (f"Connected to {self.describe()}. "
149
- "No tasks are assigned to it right now.")
149
+ "No tasks are waiting for it right now.")
150
150
  preview = ", ".join(f"{task.key} {task.summary}" for task in tasks[:3])
151
151
  more = f", +{len(tasks) - 3} more" if len(tasks) > 3 else ""
152
152
  return (f"Connected to {self.describe()}. "
@@ -1,7 +1,7 @@
1
1
  """Read-only Azure DevOps tasks provider, authenticated through Entra ID.
2
2
 
3
- Every call here is a read: a WIQL query for the ids assigned to the connected
4
- account, then a batch fetch of those work items. The WIQL endpoint is a POST,
3
+ Every call here is a read: a WIQL query for the ids of the Codee work items in
4
+ the states the skills trigger on, then a batch fetch of those work items. The WIQL endpoint is a POST,
5
5
  but it is a query — nothing in this module creates or modifies a work item.
6
6
  """
7
7
  from urllib.parse import quote
@@ -36,8 +36,8 @@ _FIELDS = [
36
36
  # Which Azure DevOps work item types Codee picks up, and what each stands for,
37
37
  # is configured per installation in Settings — organizations disagree on the
38
38
  # names ("User Story" in Agile, "Product Backlog Item" in Scrum, anything at
39
- # all in a custom process). Anything else assigned to the connected account
40
- # belongs to a human and is left alone.
39
+ # all in a custom process). A work item of any other type belongs to a human
40
+ # and is left alone, whatever state it reaches.
41
41
 
42
42
  # Azure DevOps priority is 1-4 with 1 highest; the executor logs this next to
43
43
  # JIRA-style names, so translate rather than print a bare digit.
@@ -122,7 +122,7 @@ class AzureDevOpsWorkItem(Task):
122
122
 
123
123
 
124
124
  class AzureDevOpsTasksProvider(AbstractTasksProvider):
125
- """Fetches work items assigned to the connected account as provider-agnostic Tasks."""
125
+ """Fetches the organization's Codee work items as provider-agnostic Tasks."""
126
126
 
127
127
  DISPLAY_NAME = "Azure DevOps"
128
128
  MCP_SERVER_NAME = "ado"
@@ -157,11 +157,14 @@ class AzureDevOpsTasksProvider(AbstractTasksProvider):
157
157
  connection = self._auth.connection() or {}
158
158
  account = connection.get("account") or "connected account"
159
159
  types = ", ".join(self._work_item_types.values()) or "no work item types"
160
+ # The account is named as the identity the query runs as, not as a
161
+ # filter — the poll matches on type and state, whoever a work item is
162
+ # assigned to.
160
163
  # The filter only gets a mention when there is one: it is off for most
161
164
  # installations, and "filter none" reads like a setting gone wrong.
162
165
  extra = f", filter {self._task_filter}" if self._task_filter else ""
163
166
  return (f"Azure DevOps {self._config.organization_url} "
164
- f"(all projects, assignee {account}, types {types}{extra})")
167
+ f"(all projects, connected as {account}, types {types}{extra})")
165
168
 
166
169
  def mcp_server(self) -> McpServer | None:
167
170
  """Microsoft's Azure DevOps MCP server, addressed at this organization.
@@ -204,7 +207,7 @@ class AzureDevOpsTasksProvider(AbstractTasksProvider):
204
207
  return [
205
208
  f'Create a new "{item_type}" work item in the {organization} '
206
209
  "organization, in any project you can create work items in, with "
207
- f'the title "{summary}", assigned to {account}.',
210
+ f'the title "{summary}".',
208
211
  "Move that work item to a Done, Closed or Removed state — "
209
212
  "whichever its board offers — so it does not stay open.",
210
213
  ]
@@ -280,7 +283,7 @@ class AzureDevOpsTasksProvider(AbstractTasksProvider):
280
283
 
281
284
  def get_tasks(self, statuses: list[str],
282
285
  raise_errors: bool = False) -> list[Task]:
283
- """Fetch work items assigned to the connected account in the given states."""
286
+ """Fetch the Codee work items sitting in the given states."""
284
287
  # Nothing is waiting on a work item, so there is no request worth
285
288
  # making. The settings check passes no statuses too, but there the whole
286
289
  # point is to reach Azure DevOps, so it queries without a state filter.
@@ -342,29 +345,39 @@ class AzureDevOpsTasksProvider(AbstractTasksProvider):
342
345
  """The state filter, dropped entirely when nothing was requested.
343
346
 
344
347
  ``IN ()`` is not valid WIQL, and the only caller that passes no statuses
345
- is the connection check — it wants every Codee work item assigned to the
346
- account, whatever state it sits in.
348
+ is the connection check — it wants every Codee work item it can see,
349
+ whatever state it sits in.
347
350
  """
348
351
  if not statuses:
349
352
  return ""
350
353
  quoted = ", ".join(_quote_wiql(status) for status in statuses)
351
- return f"AND [System.State] IN ({quoted}) "
354
+ return f"[System.State] IN ({quoted})"
352
355
 
353
356
  def _build_wiql(self, statuses: list[str]) -> str:
354
- """WIQL for Codee work items owned by the connected account, highest priority first.
355
-
356
- Nothing here names a project, and no ``@project`` macro is used — that
357
- is what keeps the query valid with no project in the route, so it spans
358
- the organization. What comes back is still narrow: only the Codee work
359
- item types, only the states asked for, and only what is assigned to the
360
- connected account.
357
+ """WIQL for Codee work items, highest priority first, then oldest.
358
+
359
+ There is no assignee clause: what hands a work item to Codee is its
360
+ type and its state, not who it is assigned to. So a work item a human
361
+ still owns is picked up the moment it reaches a state one of the skills
362
+ triggers on, which is what makes those states the handover — they have
363
+ to be ones only Codee's workflow uses. An installation that does want
364
+ an owner filter writes one as the custom WIQL in Settings, e.g.
365
+ ``[System.AssignedTo] = @Me``.
366
+
367
+ Nothing here names a project either, and no ``@project`` macro is used
368
+ — that is what keeps the query valid with no project in the route, so
369
+ it spans the organization. What comes back is still narrow: only the
370
+ Codee work item types, and only the states asked for.
361
371
  """
372
+ clauses = [clause for clause in (
373
+ self._build_wiql_type_clause(),
374
+ self._build_wiql_status_clause(statuses),
375
+ self._build_wiql_filter_clause(),
376
+ ) if clause]
377
+ where = f"WHERE {' AND '.join(clauses)} " if clauses else ""
362
378
  return (
363
379
  "SELECT [System.Id] FROM WorkItems "
364
- "WHERE [System.AssignedTo] = @Me "
365
- f"{self._build_wiql_type_clause()}"
366
- f"{self._build_wiql_status_clause(statuses)}"
367
- f"{self._build_wiql_filter_clause()}"
380
+ f"{where}"
368
381
  "ORDER BY [Microsoft.VSTS.Common.Priority] ASC, [System.CreatedDate] ASC"
369
382
  )
370
383
 
@@ -373,25 +386,26 @@ class AzureDevOpsTasksProvider(AbstractTasksProvider):
373
386
 
374
387
  Bracketed, because a filter is a whole condition rather than a single
375
388
  term: an unparenthesized ``... OR ...`` would bind its OR across the
376
- assignee and type clauses and hand back items Codee does not own.
389
+ type and state clauses and hand back items Codee does not own.
377
390
  """
378
391
  if not self._task_filter:
379
392
  return ""
380
- return f"AND ({self._task_filter}) "
393
+ return f"({self._task_filter})"
381
394
 
382
395
  def _build_wiql_type_clause(self) -> str:
383
396
  """The work item type filter, from the work items configured in Settings.
384
397
 
385
398
  Dropped when nothing is mapped, like the state clause above and for the
386
- same reason — but this one dropping is worse than a wide query: it would
387
- hand the executor every item assigned to the account. Settings keeps the
388
- mandatory work items filled in so it cannot happen in practice.
399
+ same reason — but this one dropping is worse than a wide query: with no
400
+ assignee clause to fall back on it would hand the executor every item in
401
+ the organization. Settings keeps the mandatory work items filled in so
402
+ it cannot happen in practice.
389
403
  """
390
404
  item_types = list(self._work_item_types.values())
391
405
  if not item_types:
392
406
  return ""
393
407
  quoted = ", ".join(_quote_wiql(item_type) for item_type in item_types)
394
- return f"AND [System.WorkItemType] IN ({quoted}) "
408
+ return f"[System.WorkItemType] IN ({quoted})"
395
409
 
396
410
  def _fetch_work_items(self, token: str, ids: list[int]) -> list[dict]:
397
411
  """Batch-fetch the requested work items. Organization-scoped, as the API requires."""
@@ -339,9 +339,16 @@ class TasksProviderTest(unittest.TestCase):
339
339
  wiql = self.provider._build_wiql(["Ready", "Bob's queue"])
340
340
 
341
341
  self.assertIn("[System.State] IN ('Ready', 'Bob''s queue')", wiql)
342
- self.assertIn("[System.AssignedTo] = @Me", wiql)
343
342
  self.assertIn("ORDER BY [Microsoft.VSTS.Common.Priority] ASC", wiql)
344
343
 
344
+ def test_the_query_does_not_filter_on_an_assignee(self) -> None:
345
+ # Type and state are the handover; who the work item is assigned
346
+ # to is nobody's business but the humans working alongside it.
347
+ wiql = self.provider._build_wiql(["Ready"])
348
+
349
+ self.assertNotIn("System.AssignedTo", wiql)
350
+ self.assertNotIn("@Me", wiql)
351
+
345
352
  def test_wiql_only_asks_for_the_mapped_work_item_types(self) -> None:
346
353
  wiql = self.provider._build_wiql(["Ready"])
347
354
 
@@ -534,7 +541,7 @@ class TasksProviderTest(unittest.TestCase):
534
541
  wiql = self.provider._build_wiql([])
535
542
 
536
543
  self.assertNotIn("[System.State] IN", wiql)
537
- self.assertIn("[System.AssignedTo] = @Me", wiql)
544
+ self.assertIn("[System.WorkItemType] IN", wiql)
538
545
 
539
546
  def test_a_successful_pull_names_the_work_items_it_found(self) -> None:
540
547
  self._connect()
@@ -614,7 +621,8 @@ class AzureDevOpsMcpTest(unittest.TestCase):
614
621
  self.assertIn('"Task" work item', steps[0])
615
622
  self.assertIn("acme organization", steps[0])
616
623
  self.assertIn('title "Codee check 1234"', steps[0])
617
- self.assertIn("assigned to dev@acme.com", steps[0])
624
+ # Nothing is said about the assignee: the poll does not filter on one.
625
+ self.assertNotIn("assigned to", steps[0])
618
626
  self.assertIn("Done, Closed or Removed", steps[1])
619
627
 
620
628
  def test_no_check_steps_before_the_account_is_known(self) -> None:
@@ -174,7 +174,7 @@ class JiraTasksProvider(AbstractTasksProvider):
174
174
 
175
175
  def get_tasks(self, statuses: list[str],
176
176
  raise_errors: bool = False) -> list[Task]:
177
- """Fetch tasks assigned to the target user in the configured states."""
177
+ """Fetch the Codee issues sitting in the configured statuses."""
178
178
  # Nothing is waiting on an issue, so there is no request worth making.
179
179
  # The settings check passes no statuses too, but there the whole point
180
180
  # is to reach JIRA, so it queries without a status filter.
File without changes
File without changes