devloop 0.8.0__tar.gz → 0.9.0__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 (157) hide show
  1. {devloop-0.8.0 → devloop-0.9.0}/PKG-INFO +2 -1
  2. {devloop-0.8.0 → devloop-0.9.0}/pyproject.toml +5 -4
  3. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/agent_health_monitor.py +0 -1
  4. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/formatter.py +5 -4
  5. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/git_commit_assistant.py +7 -5
  6. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/linter.py +2 -1
  7. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/test_runner.py +2 -1
  8. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/marketplace.py +0 -1
  9. devloop-0.9.0/src/devloop/cli/commands/mcp_server.py +186 -0
  10. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/metrics.py +6 -6
  11. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/main.py +388 -198
  12. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/agent_template.py +1 -1
  13. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/backup_manager.py +3 -3
  14. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/claude_adapter.py +5 -8
  15. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/config.py +3 -3
  16. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/context_store.py +1 -1
  17. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/custom_agent.py +2 -1
  18. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/event_store.py +20 -40
  19. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/learning.py +4 -2
  20. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/manager.py +7 -7
  21. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/project_context.py +2 -2
  22. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/marketplace/cache.py +3 -3
  23. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/marketplace/installer.py +0 -1
  24. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/marketplace/registry.py +0 -1
  25. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/marketplace/registry_client.py +2 -2
  26. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/marketplace/reviews.py +0 -1
  27. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/marketplace/search.py +4 -4
  28. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/marketplace/signing.py +3 -2
  29. devloop-0.9.0/src/devloop/mcp/__init__.py +5 -0
  30. devloop-0.9.0/src/devloop/mcp/resources.py +277 -0
  31. devloop-0.9.0/src/devloop/mcp/server.py +624 -0
  32. devloop-0.9.0/src/devloop/mcp/subscriptions.py +187 -0
  33. devloop-0.9.0/src/devloop/mcp/tools.py +1039 -0
  34. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/providers/artifactory_registry.py +1 -1
  35. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/providers/jenkins_provider.py +1 -1
  36. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/providers/provider_manager.py +3 -3
  37. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/security/factory.py +12 -12
  38. {devloop-0.8.0 → devloop-0.9.0}/LICENSE +0 -0
  39. {devloop-0.8.0 → devloop-0.9.0}/README.md +0 -0
  40. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/__init__.py +0 -0
  41. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/__init__.py +0 -0
  42. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/ci_monitor.py +0 -0
  43. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/code_rabbit.py +0 -0
  44. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/doc_lifecycle.py +0 -0
  45. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/echo.py +0 -0
  46. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/file_logger.py +0 -0
  47. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/performance_profiler.py +0 -0
  48. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/sandbox_helper.py +0 -0
  49. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/security_scanner.py +0 -0
  50. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/snyk.py +0 -0
  51. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/agents/type_checker.py +0 -0
  52. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/__init__.py +0 -0
  53. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/agent_rules.py +0 -0
  54. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/coderabbit_installer.py +0 -0
  55. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/__init__.py +0 -0
  56. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/agent_publish.py +0 -0
  57. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/audit.py +0 -0
  58. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/custom_agents.py +0 -0
  59. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/feedback.py +0 -0
  60. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/insights.py +0 -0
  61. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/marketplace_server.py +0 -0
  62. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/release.py +0 -0
  63. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/summary.py +0 -0
  64. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/telemetry.py +0 -0
  65. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/commands/tools.py +0 -0
  66. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/main_v1.py +0 -0
  67. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/pre_push_check.py +0 -0
  68. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/prerequisites.py +0 -0
  69. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/pyodide_installer.py +0 -0
  70. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/snyk_installer.py +0 -0
  71. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/.devloop/tools-registry.json +0 -0
  72. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/claude_commands/README.md +0 -0
  73. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/claude_commands/agent-summary.md +0 -0
  74. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/claude_commands/devloop-findings.md +0 -0
  75. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/claude_commands/devloop-status.md +0 -0
  76. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/claude_commands/extract-findings.md +0 -0
  77. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/claude_commands/verify-work.md +0 -0
  78. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/devloop_agents_template.md +0 -0
  79. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/git_hooks/post-commit +0 -0
  80. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/git_hooks/pre-commit +0 -0
  81. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/git_hooks/pre-commit-checks +0 -0
  82. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/git_hooks/pre-push +0 -0
  83. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/supervisor/devloop.conf +0 -0
  84. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/cli/templates/systemd/devloop.service +0 -0
  85. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/collectors/__init__.py +0 -0
  86. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/collectors/base.py +0 -0
  87. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/collectors/filesystem.py +0 -0
  88. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/collectors/git.py +0 -0
  89. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/collectors/manager.py +0 -0
  90. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/collectors/process.py +0 -0
  91. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/collectors/system.py +0 -0
  92. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/__init__.py +0 -0
  93. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/action_logger.py +0 -0
  94. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/agent.py +0 -0
  95. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/agent_audit_logger.py +0 -0
  96. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/amp_integration.py +0 -0
  97. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/amp_thread_mapper.py +0 -0
  98. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/auto_fix.py +0 -0
  99. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/config_schema.py +0 -0
  100. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/context.py +0 -0
  101. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/contextual_feedback.py +0 -0
  102. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/daemon_health.py +0 -0
  103. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/debug_trace.py +0 -0
  104. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/error_handler.py +0 -0
  105. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/error_notifier.py +0 -0
  106. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/event.py +0 -0
  107. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/event_replayer.py +0 -0
  108. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/feedback.py +0 -0
  109. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/file_lock_manager.py +0 -0
  110. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/operational_health.py +0 -0
  111. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/pattern_analyzer.py +0 -0
  112. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/pattern_detector.py +0 -0
  113. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/performance.py +0 -0
  114. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/proactive_feedback.py +0 -0
  115. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/summary_formatter.py +0 -0
  116. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/summary_generator.py +0 -0
  117. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/telemetry.py +0 -0
  118. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/tool_dependencies.py +0 -0
  119. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/tool_registry.py +0 -0
  120. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/tool_runner.py +0 -0
  121. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/core/transactional_io.py +0 -0
  122. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/integrations/__init__.py +0 -0
  123. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/integrations/beads_integration.py +0 -0
  124. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/lsp/__init__.py +0 -0
  125. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/lsp/__main__.py +0 -0
  126. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/lsp/mapper.py +0 -0
  127. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/lsp/server.py +0 -0
  128. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/marketplace/__init__.py +0 -0
  129. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/marketplace/api.py +0 -0
  130. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/marketplace/http_server.py +0 -0
  131. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/marketplace/metadata.py +0 -0
  132. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/marketplace/publisher.py +0 -0
  133. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/metrics/__init__.py +0 -0
  134. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/metrics/dora.py +0 -0
  135. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/metrics/value_metrics.py +0 -0
  136. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/providers/__init__.py +0 -0
  137. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/providers/ci_provider.py +0 -0
  138. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/providers/circleci_provider.py +0 -0
  139. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/providers/github_actions_provider.py +0 -0
  140. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/providers/gitlab_ci_provider.py +0 -0
  141. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/providers/pypi_registry.py +0 -0
  142. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/providers/registry_provider.py +0 -0
  143. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/release/__init__.py +0 -0
  144. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/release/release_manager.py +0 -0
  145. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/security/__init__.py +0 -0
  146. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/security/audit_logger.py +0 -0
  147. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/security/bubblewrap_sandbox.py +0 -0
  148. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/security/cgroups_helper.py +0 -0
  149. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/security/no_sandbox.py +0 -0
  150. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/security/package.json +0 -0
  151. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/security/path_validator.py +0 -0
  152. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/security/pyodide_runner.js +0 -0
  153. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/security/pyodide_sandbox.py +0 -0
  154. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/security/sandbox.py +0 -0
  155. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/security/token_manager.py +0 -0
  156. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/telemetry/__init__.py +0 -0
  157. {devloop-0.8.0 → devloop-0.9.0}/src/devloop/telemetry/telemetry_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devloop
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: Intelligent background agents for development workflow automation
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -28,6 +28,7 @@ Provides-Extra: marketplace-api
28
28
  Provides-Extra: snyk
29
29
  Requires-Dist: aiofiles (>=23.2,<24.0)
30
30
  Requires-Dist: lsprotocol (>=2023.0.0,<2024.0.0)
31
+ Requires-Dist: mcp (>=1.0.0,<2.0.0)
31
32
  Requires-Dist: psutil (>=5.9,<6.0)
32
33
  Requires-Dist: pydantic (>=2.5,<3.0)
33
34
  Requires-Dist: pygls (>=1.3.0,<2.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "devloop"
3
- version = "0.8.0"
3
+ version = "0.9.0"
4
4
  description = "Intelligent background agents for development workflow automation"
5
5
  authors = ["DevLoop Contributors <devloop@example.com>"]
6
6
  license = "MIT"
@@ -49,12 +49,13 @@ aiofiles = "^23.2"
49
49
  psutil = "^5.9"
50
50
  pygls = "^1.3.0"
51
51
  lsprotocol = "^2023.0.0"
52
+ mcp = "^1.0.0"
52
53
 
53
54
  [tool.poetry.group.dev.dependencies]
54
55
  pytest = "^7.4"
55
56
  pytest-asyncio = "^0.21"
56
57
  pytest-cov = "^4.1"
57
- black = "^23.12"
58
+ black = ">=24.3.0"
58
59
  ruff = "^0.1"
59
60
  mypy = "^1.8"
60
61
  bandit = "^1.7"
@@ -96,7 +97,7 @@ markers = [
96
97
  "security: marks tests as security tests",
97
98
  "benchmark: marks tests as benchmarks",
98
99
  ]
99
- addopts = "--cov=src/devloop --cov-report=term-missing --cov-report=html --cov-report=xml"
100
+ addopts = "--import-mode=importlib --cov=src/devloop --cov-report=term-missing --cov-report=html --cov-report=xml"
100
101
 
101
102
  [tool.coverage.run]
102
103
  source = ["src/devloop"]
@@ -123,7 +124,7 @@ exclude_lines = [
123
124
 
124
125
  [tool.mypy]
125
126
  python_version = "3.11"
126
- warn_return_any = false
127
+ warn_return_any = true
127
128
  warn_unused_configs = true
128
129
  disallow_untyped_defs = false
129
130
  disallow_incomplete_defs = false
@@ -8,7 +8,6 @@ from devloop.core.auto_fix import auto_fix
8
8
  from devloop.core.context_store import context_store, Finding
9
9
  from devloop.core.event import Event
10
10
 
11
-
12
11
  logger = logging.getLogger(__name__)
13
12
 
14
13
 
@@ -46,9 +46,9 @@ class FormatterAgent(Agent):
46
46
  self.config = FormatterConfig(config or {})
47
47
 
48
48
  # Loop prevention mechanisms
49
- self._recent_formats: Dict[
50
- str, List[float]
51
- ] = {} # file_path -> list of timestamps
49
+ self._recent_formats: Dict[str, List[float]] = (
50
+ {}
51
+ ) # file_path -> list of timestamps
52
52
  self._format_timeout = 30 # seconds
53
53
  self._loop_detection_window = 10 # seconds
54
54
  self._max_consecutive_formats = 3 # per file per window
@@ -246,7 +246,8 @@ class FormatterAgent(Agent):
246
246
 
247
247
  language = extension_map.get(suffix)
248
248
  if language:
249
- return self.config.formatters.get(language)
249
+ formatter = self.config.formatters.get(language)
250
+ return str(formatter) if formatter else None
250
251
 
251
252
  return None
252
253
 
@@ -318,17 +318,19 @@ class GitCommitAssistantAgent(Agent):
318
318
  if not self.config.auto_generate_scope:
319
319
  return ""
320
320
 
321
- modules = analysis.get("affected_modules", [])
321
+ modules: list[str] = analysis.get("affected_modules", [])
322
322
  if len(modules) == 1:
323
- return modules[0].lower().replace(" ", "-")
323
+ return str(modules[0]).lower().replace(" ", "-")
324
324
  elif len(modules) > 1:
325
325
  # Find common prefix
326
326
  common = ""
327
- for i, char in enumerate(modules[0]):
327
+ first_module = str(modules[0])
328
+ for i, char in enumerate(first_module):
328
329
  if all(
329
- module.startswith(modules[0][: i + 1]) for module in modules[1:]
330
+ str(module).startswith(first_module[: i + 1])
331
+ for module in modules[1:]
330
332
  ):
331
- common = modules[0][: i + 1]
333
+ common = first_module[: i + 1]
332
334
  else:
333
335
  break
334
336
  return common.lower().replace(" ", "-") if common else ""
@@ -180,7 +180,8 @@ class LinterAgent(Agent):
180
180
 
181
181
  language = extension_map.get(suffix)
182
182
  if language:
183
- return self.config.linters.get(language)
183
+ linter = self.config.linters.get(language)
184
+ return str(linter) if linter else None
184
185
 
185
186
  return None
186
187
 
@@ -327,7 +327,8 @@ class TestRunnerAgent(Agent):
327
327
 
328
328
  language = extension_map.get(suffix)
329
329
  if language:
330
- return self.config.test_frameworks.get(language)
330
+ framework = self.config.test_frameworks.get(language)
331
+ return str(framework) if framework else None
331
332
 
332
333
  return None
333
334
 
@@ -14,7 +14,6 @@ from devloop.marketplace import RegistryClient, RegistryConfig
14
14
  from devloop.marketplace.installer import AgentInstaller
15
15
  from devloop.marketplace.registry import AgentRegistry
16
16
 
17
-
18
17
  logger = logging.getLogger(__name__)
19
18
  app = typer.Typer(help="Agent marketplace commands", add_completion=False)
20
19
  console = Console()
@@ -0,0 +1,186 @@
1
+ """MCP server CLI command.
2
+
3
+ This module provides the CLI entry point for the DevLoop MCP server,
4
+ enabling integration with Claude Code and other MCP-compatible clients.
5
+
6
+ Usage:
7
+ devloop mcp-server # Start server (stdio mode)
8
+ devloop mcp-server --check # Validate server can start
9
+ devloop mcp-server --install # Register in Claude Code settings
10
+ devloop mcp-server --uninstall # Remove from Claude Code settings
11
+ """
12
+
13
+ import asyncio
14
+ import json
15
+ from pathlib import Path
16
+
17
+ import typer
18
+ from rich.console import Console
19
+
20
+ app = typer.Typer(name="mcp-server", help="DevLoop MCP server for Claude Code")
21
+ console = Console()
22
+
23
+
24
+ def get_claude_settings_path() -> Path:
25
+ """Get the path to Claude Code settings file.
26
+
27
+ Returns:
28
+ Path to ~/.claude/settings.json
29
+ """
30
+ return Path.home() / ".claude" / "settings.json"
31
+
32
+
33
+ def install_mcp_server() -> bool:
34
+ """Install DevLoop MCP server in Claude Code settings.
35
+
36
+ Creates or updates ~/.claude/settings.json to register the devloop
37
+ MCP server.
38
+
39
+ Returns:
40
+ True if installation succeeded, False otherwise.
41
+ """
42
+ settings_path = get_claude_settings_path()
43
+
44
+ # Create .claude directory if needed
45
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
46
+
47
+ # Load existing settings or create new
48
+ settings = {}
49
+ if settings_path.exists():
50
+ try:
51
+ settings = json.loads(settings_path.read_text())
52
+ except json.JSONDecodeError:
53
+ settings = {}
54
+
55
+ # Ensure mcpServers key exists
56
+ if "mcpServers" not in settings:
57
+ settings["mcpServers"] = {}
58
+
59
+ # Add devloop server configuration
60
+ settings["mcpServers"]["devloop"] = {
61
+ "command": "devloop",
62
+ "args": ["mcp-server"],
63
+ }
64
+
65
+ # Write settings back
66
+ settings_path.write_text(json.dumps(settings, indent=2) + "\n")
67
+
68
+ return True
69
+
70
+
71
+ def uninstall_mcp_server() -> bool:
72
+ """Remove DevLoop MCP server from Claude Code settings.
73
+
74
+ Updates ~/.claude/settings.json to remove the devloop MCP server.
75
+
76
+ Returns:
77
+ True if uninstallation succeeded, False otherwise.
78
+ """
79
+ settings_path = get_claude_settings_path()
80
+
81
+ if not settings_path.exists():
82
+ return True
83
+
84
+ try:
85
+ settings = json.loads(settings_path.read_text())
86
+ except json.JSONDecodeError:
87
+ return True
88
+
89
+ # Remove devloop from mcpServers if present
90
+ if "mcpServers" in settings and "devloop" in settings["mcpServers"]:
91
+ del settings["mcpServers"]["devloop"]
92
+
93
+ # Write settings back
94
+ settings_path.write_text(json.dumps(settings, indent=2) + "\n")
95
+
96
+ return True
97
+
98
+
99
+ @app.callback(invoke_without_command=True)
100
+ def main(
101
+ ctx: typer.Context,
102
+ check: bool = typer.Option(
103
+ False, "--check", help="Validate server can start correctly"
104
+ ),
105
+ install: bool = typer.Option(
106
+ False, "--install", help="Register in Claude Code settings"
107
+ ),
108
+ uninstall: bool = typer.Option(
109
+ False, "--uninstall", help="Remove from Claude Code settings"
110
+ ),
111
+ ) -> None:
112
+ """DevLoop MCP server for Claude Code integration.
113
+
114
+ By default, starts the MCP server in stdio mode for Claude Code communication.
115
+
116
+ Use --install to register the server in Claude Code settings, and
117
+ --uninstall to remove it.
118
+ """
119
+ # Import here to avoid circular imports and for lazy loading
120
+ from devloop.mcp.server import MCPServer
121
+
122
+ # Check for mutually exclusive options
123
+ options_set = sum([check, install, uninstall])
124
+ if options_set > 1:
125
+ console.print(
126
+ "[red]Error:[/red] Only one of --check, --install, or --uninstall can be specified"
127
+ )
128
+ raise typer.Exit(1)
129
+
130
+ if check:
131
+ # Validate server can start
132
+ try:
133
+ server = MCPServer()
134
+ console.print("[green]Server validated successfully.[/green]")
135
+ console.print(f"Project root: {server.project_root}")
136
+ except FileNotFoundError as e:
137
+ console.print(f"[red]Error:[/red] {e}")
138
+ raise typer.Exit(1)
139
+ except Exception as e:
140
+ console.print(f"[red]Error validating server:[/red] {e}")
141
+ raise typer.Exit(1)
142
+
143
+ elif install:
144
+ # Install MCP server in Claude Code settings
145
+ try:
146
+ if install_mcp_server():
147
+ settings_path = get_claude_settings_path()
148
+ console.print(
149
+ "[green]DevLoop MCP server installed successfully.[/green]"
150
+ )
151
+ console.print(f"Settings updated: {settings_path}")
152
+ console.print("\nRestart Claude Code to activate the MCP server.")
153
+ else:
154
+ console.print("[red]Failed to install MCP server.[/red]")
155
+ raise typer.Exit(1)
156
+ except Exception as e:
157
+ console.print(f"[red]Error installing MCP server:[/red] {e}")
158
+ raise typer.Exit(1)
159
+
160
+ elif uninstall:
161
+ # Remove MCP server from Claude Code settings
162
+ try:
163
+ if uninstall_mcp_server():
164
+ console.print(
165
+ "[green]DevLoop MCP server uninstalled successfully.[/green]"
166
+ )
167
+ else:
168
+ console.print("[red]Failed to uninstall MCP server.[/red]")
169
+ raise typer.Exit(1)
170
+ except Exception as e:
171
+ console.print(f"[red]Error uninstalling MCP server:[/red] {e}")
172
+ raise typer.Exit(1)
173
+
174
+ else:
175
+ # Start server in stdio mode (default behavior)
176
+ try:
177
+ server = MCPServer()
178
+ asyncio.run(server.run())
179
+ except FileNotFoundError as e:
180
+ console.print(f"[red]Error:[/red] {e}")
181
+ raise typer.Exit(1)
182
+ except KeyboardInterrupt:
183
+ pass
184
+ except Exception as e:
185
+ console.print(f"[red]Server error:[/red] {e}")
186
+ raise typer.Exit(1)
@@ -173,15 +173,15 @@ def _calculate_ci_metrics(events: list[dict]) -> dict[str, any]:
173
173
  "pre_commit_passed": pre_commit_passed,
174
174
  "pre_commit_failed": pre_commit_failed,
175
175
  "pre_commit_total": total_commits,
176
- "pre_commit_pass_rate": (pre_commit_passed / total_commits * 100)
177
- if total_commits > 0
178
- else 0,
176
+ "pre_commit_pass_rate": (
177
+ (pre_commit_passed / total_commits * 100) if total_commits > 0 else 0
178
+ ),
179
179
  "pre_push_passed": pre_push_passed,
180
180
  "pre_push_failed": pre_push_failed,
181
181
  "pre_push_total": total_pushes,
182
- "pre_push_pass_rate": (pre_push_passed / total_pushes * 100)
183
- if total_pushes > 0
184
- else 0,
182
+ "pre_push_pass_rate": (
183
+ (pre_push_passed / total_pushes * 100) if total_pushes > 0 else 0
184
+ ),
185
185
  }
186
186
 
187
187