codee-agent 0.6.7__tar.gz → 0.6.9__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 (92) hide show
  1. {codee_agent-0.6.7 → codee_agent-0.6.9}/PKG-INFO +1 -1
  2. {codee_agent-0.6.7 → codee_agent-0.6.9}/pyproject.toml +1 -1
  3. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/admin.py +30 -0
  4. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/admin_service.py +10 -0
  5. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/executor.py +21 -4
  6. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/test_admin_service.py +15 -0
  7. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/test_executor.py +62 -3
  8. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_github_copilot/provider.py +4 -0
  9. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_github_copilot/test.py +16 -0
  10. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_main_context/context.py +8 -1
  11. {codee_agent-0.6.7 → codee_agent-0.6.9}/LICENSE +0 -0
  12. {codee_agent-0.6.7 → codee_agent-0.6.9}/README.md +0 -0
  13. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/.gitignore +0 -0
  14. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/__init__.py +0 -0
  15. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/admin_api.py +0 -0
  16. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/admin_cli.py +0 -0
  17. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/agent_cli.py +0 -0
  18. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/coding_agents.py +0 -0
  19. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/init_cli.py +0 -0
  20. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/__init__.py +0 -0
  21. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/claude_key_rotation.py +0 -0
  22. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/cron_describe.py +0 -0
  23. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/mcp_config.py +0 -0
  24. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/runs_db.py +0 -0
  25. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/test_claude_key_rotation.py +0 -0
  26. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/test_mcp_config.py +0 -0
  27. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/test_runs_db.py +0 -0
  28. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/test_trigger_cron_skills.py +0 -0
  29. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/test_trigger_issue_skills.py +0 -0
  30. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/trigger_aws_sqs_skills.py +0 -0
  31. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/trigger_cron_skills.py +0 -0
  32. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/trigger_email_skills.py +0 -0
  33. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/lib/trigger_issue_skills.py +0 -0
  34. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/mail_server.py +0 -0
  35. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/setup_wizard.py +0 -0
  36. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/start_cli.py +0 -0
  37. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/tasks_providers.py +0 -0
  38. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/AGENTS.md +0 -0
  39. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/CLAUDE.md +0 -0
  40. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/skills/aws-sqs-alarm-response/SKILL.md +0 -0
  41. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/skills/cron-research-5xx-errors/SKILL.md +0 -0
  42. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/skills/story-code-reviewer/SKILL.md +0 -0
  43. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/skills/story-developer/SKILL.md +0 -0
  44. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/skills/story-planner/SKILL.md +0 -0
  45. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/skills/story-planner/assets/readme-template.md +0 -0
  46. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/skills/story-qa/SKILL.md +0 -0
  47. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/skills/story-security-reviewer/SKILL.md +0 -0
  48. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/skills/task-code-reviewer/SKILL.md +0 -0
  49. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/skills/task-developer/SKILL.md +0 -0
  50. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/skills/task-qa/SKILL.md +0 -0
  51. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/templates/skills/task-security-reviewer/SKILL.md +0 -0
  52. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/test_admin_api.py +0 -0
  53. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/test_admin_cli.py +0 -0
  54. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/test_agent_cli.py +0 -0
  55. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/test_coding_agents.py +0 -0
  56. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/test_init_cli.py +0 -0
  57. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/test_memory_index.py +0 -0
  58. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/test_setup_wizard.py +0 -0
  59. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/test_start_cli.py +0 -0
  60. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee/workflow_graph.py +0 -0
  61. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_admin/__init__.py +0 -0
  62. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_admin/codee_admin.py +0 -0
  63. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_abstract/__init__.py +0 -0
  64. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_abstract/provider.py +0 -0
  65. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_claude_code/__init__.py +0 -0
  66. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_claude_code/account.py +0 -0
  67. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_claude_code/credentials.py +0 -0
  68. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_claude_code/oauth.py +0 -0
  69. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_claude_code/provider.py +0 -0
  70. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_claude_code/test.py +0 -0
  71. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_claude_code/usage.py +0 -0
  72. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_codex/__init__.py +0 -0
  73. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_codex/provider.py +0 -0
  74. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_codex/test.py +0 -0
  75. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_agent_github_copilot/__init__.py +0 -0
  76. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_database/__init__.py +0 -0
  77. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_database/claude_code_accounts.py +0 -0
  78. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_database/database.py +0 -0
  79. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_database/oauth_tokens.py +0 -0
  80. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_main_context/__init__.py +0 -0
  81. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_main_context/logging.py +0 -0
  82. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_main_context/test_context.py +0 -0
  83. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_main_context/test_logging.py +0 -0
  84. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_tasks_abstract/__init__.py +0 -0
  85. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_tasks_abstract/provider.py +0 -0
  86. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_tasks_azure_devops/__init__.py +0 -0
  87. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_tasks_azure_devops/oauth.py +0 -0
  88. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_tasks_azure_devops/provider.py +0 -0
  89. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_tasks_azure_devops/test.py +0 -0
  90. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_tasks_jira/__init__.py +0 -0
  91. {codee_agent-0.6.7 → codee_agent-0.6.9}/src/codee_tasks_jira/provider.py +0 -0
  92. {codee_agent-0.6.7 → codee_agent-0.6.9}/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.6.7
3
+ Version: 0.6.9
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,codex,automation
6
6
  Author: Denis Kibalko
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codee-agent"
3
- version = "0.6.7"
3
+ version = "0.6.9"
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"
@@ -38,6 +38,7 @@ USAGE_POLL_INTERVAL = 60
38
38
  # installed or uninstalled under a running Codee, and the answer shapes the
39
39
  # page rather than anything the user can change on it.
40
40
  CLAUDE_CODE_AVAILABLE = SERVICE.claude_code_available()
41
+ GITHUB_COPILOT_AVAILABLE = SERVICE.github_copilot_available()
41
42
 
42
43
 
43
44
  def _skill_summary(skill: dict[str, str]) -> SkillSummary:
@@ -361,6 +362,7 @@ class AdminState(rx.State):
361
362
 
362
363
  tasks_provider: str = "jira"
363
364
  coding_agent: str = "claude_code"
365
+ github_copilot_prompt_prefix: str = ""
364
366
  max_parallel_agents: str = "3"
365
367
  agent_test_open: bool = False
366
368
  agent_test_input: str = ""
@@ -973,6 +975,7 @@ class AdminState(rx.State):
973
975
  settings = SERVICE.load_settings()
974
976
  self.tasks_provider = settings.tasks_provider.value
975
977
  self.coding_agent = settings.coding_agent.value
978
+ self.github_copilot_prompt_prefix = settings.github_copilot_prompt_prefix
976
979
  self.max_parallel_agents = str(settings.max_parallel_agents)
977
980
  self.claude_code_rotate_keys = settings.claude_code_rotate_keys
978
981
  self._cancel_claude_code_sign_in()
@@ -1207,6 +1210,9 @@ class AdminState(rx.State):
1207
1210
  def set_coding_agent(self, value: str) -> None:
1208
1211
  self.coding_agent = value
1209
1212
 
1213
+ def set_github_copilot_prompt_prefix(self, value: str) -> None:
1214
+ self.github_copilot_prompt_prefix = value
1215
+
1210
1216
  def set_max_parallel_agents(self, value: str) -> None:
1211
1217
  self.max_parallel_agents = value
1212
1218
 
@@ -1549,6 +1555,7 @@ class AdminState(rx.State):
1549
1555
  work_item_queries,
1550
1556
  self._task_filter(),
1551
1557
  self.claude_code_rotate_keys,
1558
+ self.github_copilot_prompt_prefix,
1552
1559
  )
1553
1560
  # Saving is what picks the first account when rotation has just been
1554
1561
  # switched on, so the badge has to be redrawn from what that decided.
@@ -3363,6 +3370,28 @@ def claude_code_setting() -> rx.Component:
3363
3370
  padding="1.25rem", background=SURFACE, border=BORDER, width="100%")
3364
3371
 
3365
3372
 
3373
+ def github_copilot_setting() -> rx.Component:
3374
+ """Settings used only by GitHub Copilot CLI runs."""
3375
+ return rx.box(
3376
+ rx.heading("GitHub Copilot", size="4", margin_bottom="1rem"),
3377
+ field(
3378
+ "Prompt prefix",
3379
+ rx.text_area(
3380
+ value=AdminState.github_copilot_prompt_prefix,
3381
+ on_change=AdminState.set_github_copilot_prompt_prefix,
3382
+ placeholder="Instructions added before every Copilot prompt",
3383
+ width="100%",
3384
+ min_height="7rem",
3385
+ ),
3386
+ rx.text(
3387
+ "Added to the beginning of every prompt sent to GitHub Copilot.",
3388
+ color=MUTED,
3389
+ font_size="0.82rem",
3390
+ ),
3391
+ ),
3392
+ padding="1.25rem", background=SURFACE, border=BORDER, width="100%")
3393
+
3394
+
3366
3395
  def conversation_message(message: ConversationMessage) -> rx.Component:
3367
3396
  is_user = message.role == "user"
3368
3397
  return rx.box(
@@ -3540,6 +3569,7 @@ def settings_page() -> rx.Component:
3540
3569
  # which agents a machine has is not state: there is no event that could
3541
3570
  # ever flip it, and nothing on the page should be able to.
3542
3571
  claude_code_setting() if CLAUDE_CODE_AVAILABLE else rx.fragment(),
3572
+ github_copilot_setting() if GITHUB_COPILOT_AVAILABLE else rx.fragment(),
3543
3573
  rx.box(
3544
3574
  rx.heading("Tasks provider", size="4", margin_bottom="1rem"),
3545
3575
  field("Provider", rx.select(["jira", "azure_devops"], value=AdminState.tasks_provider,
@@ -26,6 +26,7 @@ from codee_agent_claude_code.provider import ClaudeCodeAgent
26
26
  from codee_agent_claude_code.usage import (
27
27
  SESSION_WINDOW, WEEKLY_WINDOW, UsageRateLimited, UsageUnavailable,
28
28
  fetch_usage)
29
+ from codee_agent_github_copilot.provider import GitHubCopilotAgent
29
30
  from codee_tasks_abstract.provider import (
30
31
  AbstractTasksProvider, TasksProviderError)
31
32
  from codee.coding_agents import (
@@ -2109,6 +2110,7 @@ class AdminService:
2109
2110
  work_item_queries: dict[str, str] | None = None,
2110
2111
  task_filter: str = "",
2111
2112
  claude_code_rotate_keys: bool = False,
2113
+ github_copilot_prompt_prefix: str | None = None,
2112
2114
  ) -> None:
2113
2115
  current = self.context.settings
2114
2116
  all_credentials = dict(current.credentials)
@@ -2132,6 +2134,10 @@ class AdminService:
2132
2134
  self.context.settings = Settings(
2133
2135
  tasks_provider=TasksProvider(tasks_provider),
2134
2136
  coding_agent=CodingAgent(coding_agent),
2137
+ github_copilot_prompt_prefix=(
2138
+ current.github_copilot_prompt_prefix
2139
+ if github_copilot_prompt_prefix is None
2140
+ else github_copilot_prompt_prefix.strip()),
2135
2141
  credentials=all_credentials,
2136
2142
  work_item_types=all_work_items,
2137
2143
  work_item_queries=all_work_item_queries,
@@ -2152,6 +2158,10 @@ class AdminService:
2152
2158
  """
2153
2159
  return ClaudeCodeAgent.is_installed()
2154
2160
 
2161
+ def github_copilot_available(self) -> bool:
2162
+ """Whether this machine has the GitHub Copilot CLI installed."""
2163
+ return GitHubCopilotAgent.is_installed()
2164
+
2155
2165
  def claude_code_accounts(self) -> list[ConnectedAccount]:
2156
2166
  """The connected accounts, in rotation order, for the settings page."""
2157
2167
  try:
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import os
2
3
  import subprocess
3
4
  import threading
4
5
  import time
@@ -9,11 +10,12 @@ from datetime import datetime, timezone
9
10
  from codee_agent_abstract.provider import AbstractCodingAgent
10
11
  from codee_agent_claude_code.provider import ClaudeCodeAgent
11
12
  from codee_agent_codex.provider import CodexAgent
12
- from codee_agent_github_copilot.provider import GitHubCopilotAgent
13
+ from codee_agent_github_copilot.provider import (
14
+ COPILOT_DEBUG_ENV_VAR, GitHubCopilotAgent)
13
15
  from codee_main_context.context import (
14
16
  CodeeMainContext, CodingAgent, Settings, codee_issue_types, data_dir,
15
17
  load_settings, project_root)
16
- from codee_main_context.logging import configure_logging, get_logger
18
+ from codee_main_context.logging import DEBUG_ENV_VAR, configure_logging, get_logger
17
19
  from codee_tasks_abstract.provider import AbstractTasksProvider
18
20
 
19
21
  from codee.coding_agents import resolve_agent_code
@@ -70,6 +72,13 @@ def _max_parallel_agents() -> int:
70
72
  return max(1, context.settings.max_parallel_agents)
71
73
 
72
74
 
75
+ def _log_debug_environment() -> None:
76
+ if os.environ.get(COPILOT_DEBUG_ENV_VAR, "").strip().lower() == "true":
77
+ log.info("COPILOT_DEBUG=true (Copilot CLI debug logs enabled)")
78
+ if os.environ.get(DEBUG_ENV_VAR, "").strip() == "1":
79
+ log.info("CODEE_DEBUG=1 (Codee debug logging enabled)")
80
+
81
+
73
82
  def _load_sessions() -> dict[str, str]:
74
83
  """Load task_id -> session_id mapping from disk."""
75
84
  if SESSIONS_FILE.exists():
@@ -171,7 +180,13 @@ def _refresh_config() -> None:
171
180
  else:
172
181
  log.info("Reloaded tasks provider: %s", tasks_provider.describe())
173
182
 
174
- if settings.coding_agent != previous.coding_agent:
183
+ coding_agent_changed = settings.coding_agent != previous.coding_agent
184
+ copilot_settings_changed = (
185
+ settings.coding_agent == CodingAgent.GITHUB_COPILOT
186
+ and settings.github_copilot_prompt_prefix
187
+ != previous.github_copilot_prompt_prefix
188
+ )
189
+ if coding_agent_changed or copilot_settings_changed:
175
190
  try:
176
191
  coding_agent = _build_coding_agent(settings)
177
192
  except Exception as exc:
@@ -276,7 +291,8 @@ def _run_agent(user_message: str, session_id: str, model: str = "",
276
291
  """
277
292
  if agent_session_id == session_id:
278
293
  return
279
- log.debug("job %s runs under agent session %s", job_id, agent_session_id)
294
+ log.debug("job %s runs under agent session %s",
295
+ job_id, agent_session_id)
280
296
  runs_db.note_agent_session(session_id, agent_session_id)
281
297
  runs_db.set_job_session(job_id, agent_session_id, main_context=context)
282
298
 
@@ -460,6 +476,7 @@ def main() -> None:
460
476
  # Entry point: install the handler before anything logs. Level comes from
461
477
  # CODEE_DEBUG, which `codee-start --debug` exports for this subprocess.
462
478
  configure_logging()
479
+ _log_debug_environment()
463
480
 
464
481
  if not tasks_provider.is_configured():
465
482
  log.warning("tasks provider is not configured; task polling stays "
@@ -258,6 +258,21 @@ class AdminServiceWorkItemsTest(unittest.TestCase):
258
258
 
259
259
  self.assertEqual(load_settings(directory).task_filters["jira"], "")
260
260
 
261
+ def test_saving_preserves_the_github_copilot_prompt_prefix(self) -> None:
262
+ with tempfile.TemporaryDirectory() as temporary_directory:
263
+ directory = Path(temporary_directory)
264
+ service = self._service(directory)
265
+
266
+ service.save_settings(
267
+ "jira", "github_copilot", 3, {},
268
+ github_copilot_prompt_prefix="Follow repository policy.",
269
+ )
270
+
271
+ self.assertEqual(
272
+ load_settings(directory).github_copilot_prompt_prefix,
273
+ "Follow repository policy.",
274
+ )
275
+
261
276
  def test_saving_keeps_the_other_provider_s_work_item_queries(self) -> None:
262
277
  # A JQL condition means nothing to Azure DevOps, so each provider keeps
263
278
  # its own and switching between them loses neither.
@@ -19,6 +19,28 @@ from codee.lib import runs_db
19
19
  from codee.lib.trigger_issue_skills import IssueTriggeredSkill
20
20
 
21
21
 
22
+ class DebugEnvironmentLoggingTest(unittest.TestCase):
23
+ def test_enabled_debug_flags_are_logged_at_startup(self) -> None:
24
+ with patch.dict(os.environ, {
25
+ "COPILOT_DEBUG": "TRUE",
26
+ "CODEE_DEBUG": "1",
27
+ }, clear=True), patch.object(executor.log, "info") as info:
28
+ executor._log_debug_environment()
29
+
30
+ info.assert_any_call(
31
+ "COPILOT_DEBUG=true (Copilot CLI debug logs enabled)")
32
+ info.assert_any_call("CODEE_DEBUG=1 (Codee debug logging enabled)")
33
+
34
+ def test_disabled_debug_flags_are_not_logged(self) -> None:
35
+ with patch.dict(os.environ, {
36
+ "COPILOT_DEBUG": "false",
37
+ "CODEE_DEBUG": "true",
38
+ }, clear=True), patch.object(executor.log, "info") as info:
39
+ executor._log_debug_environment()
40
+
41
+ info.assert_not_called()
42
+
43
+
22
44
  class RefreshConfigTest(unittest.TestCase):
23
45
  """The executor re-reads settings.json each poll, so edits need no restart."""
24
46
 
@@ -68,7 +90,8 @@ class RefreshConfigTest(unittest.TestCase):
68
90
 
69
91
  executor._refresh_config()
70
92
 
71
- self.assertIsInstance(executor.tasks_provider, AzureDevOpsTasksProvider)
93
+ self.assertIsInstance(executor.tasks_provider,
94
+ AzureDevOpsTasksProvider)
72
95
  self.assertEqual(executor.context.settings.tasks_provider,
73
96
  TasksProvider.AZURE_DEVOPS)
74
97
 
@@ -144,6 +167,40 @@ class RefreshConfigTest(unittest.TestCase):
144
167
  self.assertEqual(executor.context.settings.coding_agent,
145
168
  CodingAgent.GITHUB_COPILOT)
146
169
 
170
+ def test_changing_copilot_prompt_prefix_rebuilds_the_default_agent(self) -> None:
171
+ self._save(coding_agent=CodingAgent.GITHUB_COPILOT)
172
+ executor._refresh_config()
173
+ original = executor.coding_agent
174
+
175
+ self._save(
176
+ coding_agent=CodingAgent.GITHUB_COPILOT,
177
+ github_copilot_prompt_prefix="Follow repository policy.",
178
+ )
179
+ executor._refresh_config()
180
+
181
+ self.assertIsNot(executor.coding_agent, original)
182
+ completed = Mock(
183
+ returncode=0,
184
+ stdout=(
185
+ '{"type":"assistant.message","data":{"content":"ok"}}\n'
186
+ '{"type":"result","exitCode":0}\n'
187
+ ),
188
+ stderr="",
189
+ )
190
+ issue_prompt = (
191
+ "Read .claude/skills/story-qa/SKILL.md and follow its "
192
+ "instructions exactly. STORY_ID = 92146"
193
+ )
194
+ with patch("codee_agent_github_copilot.provider.subprocess.run",
195
+ return_value=completed) as run:
196
+ executor._agent_for_skill("").run(issue_prompt, "session-1")
197
+
198
+ command = run.call_args.args[0]
199
+ self.assertEqual(
200
+ command[command.index("-p") + 1],
201
+ f"Follow repository policy.\n\n{issue_prompt}",
202
+ )
203
+
147
204
  def test_broken_coding_agent_settings_keep_polling_with_the_old_one(self) -> None:
148
205
  self._save(coding_agent=CodingAgent.CLAUDE_CODE)
149
206
  executor._refresh_config()
@@ -446,10 +503,12 @@ class ParentWorkItemSkipTest(unittest.TestCase):
446
503
  self.provider.is_configured.return_value = True
447
504
  self.skills = [
448
505
  IssueTriggeredSkill(name="Task developer", slug="task-developer",
449
- path=Path("/repo/.claude/skills/task/SKILL.md"),
506
+ path=Path(
507
+ "/repo/.claude/skills/task/SKILL.md"),
450
508
  statuses=("Ready",), issue_type="task"),
451
509
  IssueTriggeredSkill(name="Story developer", slug="story-developer",
452
- path=Path("/repo/.claude/skills/story/SKILL.md"),
510
+ path=Path(
511
+ "/repo/.claude/skills/story/SKILL.md"),
453
512
  statuses=("Ready",), issue_type="story"),
454
513
  ]
455
514
  for name in ("trigger_cron_skills", "trigger_aws_sqs_skills",
@@ -67,6 +67,7 @@ class GitHubCopilotAgent(AbstractCodingAgent):
67
67
 
68
68
  def __init__(self, settings: Settings, cwd: Path):
69
69
  super().__init__(settings, cwd)
70
+ self._settings = settings
70
71
 
71
72
  @classmethod
72
73
  def best_model(cls) -> str:
@@ -104,6 +105,9 @@ class GitHubCopilotAgent(AbstractCodingAgent):
104
105
  # answer is known before the run starts.
105
106
  if on_session_id:
106
107
  on_session_id(session_id)
108
+ prefix = self._settings.github_copilot_prompt_prefix.strip()
109
+ if prefix:
110
+ user_message = f"{prefix}\n\n{user_message}"
107
111
  cmd = [
108
112
  self.CLI_COMMAND,
109
113
  "-p", user_message,
@@ -122,6 +122,22 @@ class CopilotRunTest(unittest.TestCase):
122
122
  self.assertEqual(self.captured.call_args.kwargs["encoding"], "utf-8")
123
123
  self.assertEqual(self.captured.call_args.kwargs["errors"], "replace")
124
124
 
125
+ def test_prepends_the_configured_prompt_prefix(self) -> None:
126
+ self.agent = GitHubCopilotAgent(
127
+ Settings(github_copilot_prompt_prefix="Follow repository policy."),
128
+ Path("/repo"),
129
+ )
130
+ stdout = _stream(_event("assistant.message", {"content": "ok"}),
131
+ _event("result", exitCode=0))
132
+
133
+ self._run(_completed(stdout))
134
+
135
+ cmd = self.captured.call_args.args[0]
136
+ self.assertEqual(
137
+ cmd[cmd.index("-p") + 1],
138
+ "Follow repository policy.\n\n/do-it CORE-1",
139
+ )
140
+
125
141
  def test_continuing_reuses_the_same_session_id(self) -> None:
126
142
  stdout = _stream(_event("assistant.message", {"content": "ok"}),
127
143
  _event("result", exitCode=0))
@@ -142,6 +142,9 @@ class Settings:
142
142
  tasks_provider: TasksProvider = TasksProvider.JIRA
143
143
  # Which coding agent the executor drives to work on tasks.
144
144
  coding_agent: CodingAgent = CodingAgent.CLAUDE_CODE
145
+ # Text prepended to every prompt sent to GitHub Copilot. Other coding
146
+ # agents do not read this setting.
147
+ github_copilot_prompt_prefix: str = ""
145
148
  # Per-provider credentials, keyed by provider value -> {field key: value}.
146
149
  # Values for all providers are kept so switching provider preserves them.
147
150
  credentials: dict[str, dict[str, str]] = field(default_factory=dict)
@@ -156,7 +159,8 @@ class Settings:
156
159
  # mandatory, settings written before one work item could name several types
157
160
  # hold a bare string where there is now a list, and a work item selected by
158
161
  # a query ignores its types entirely.
159
- work_item_types: dict[str, dict[str, list[str]]] = field(default_factory=dict)
162
+ work_item_types: dict[str, dict[str, list[str]]
163
+ ] = field(default_factory=dict)
160
164
  # The advanced answer to the same question: a condition in the provider's
161
165
  # own query language that picks one Codee work item out of the backend,
162
166
  # keyed by provider value -> {codee work item: condition}. Set for a work
@@ -202,6 +206,8 @@ def load_settings(data_dir: Path) -> Settings:
202
206
  tasks_provider=TasksProvider(data["tasks_provider"]),
203
207
  coding_agent=CodingAgent(
204
208
  data.get("coding_agent", CodingAgent.CLAUDE_CODE.value)),
209
+ github_copilot_prompt_prefix=str(
210
+ data.get("github_copilot_prompt_prefix", "")),
205
211
  credentials=data.get("credentials", {}),
206
212
  work_item_types=data.get("work_item_types", {}),
207
213
  work_item_queries=data.get("work_item_queries", {}),
@@ -225,6 +231,7 @@ def save_settings(data_dir: Path, settings: Settings) -> None:
225
231
  payload = json.dumps({
226
232
  "tasks_provider": settings.tasks_provider.value,
227
233
  "coding_agent": settings.coding_agent.value,
234
+ "github_copilot_prompt_prefix": settings.github_copilot_prompt_prefix,
228
235
  "credentials": settings.credentials,
229
236
  "work_item_types": settings.work_item_types,
230
237
  "work_item_queries": settings.work_item_queries,
File without changes
File without changes