hatch-xclam 0.7.1.dev3__py3-none-any.whl → 0.8.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. hatch/__init__.py +1 -1
  2. hatch/cli/__init__.py +71 -0
  3. hatch/cli/__main__.py +1035 -0
  4. hatch/cli/cli_env.py +865 -0
  5. hatch/cli/cli_mcp.py +1965 -0
  6. hatch/cli/cli_package.py +566 -0
  7. hatch/cli/cli_system.py +136 -0
  8. hatch/cli/cli_utils.py +1289 -0
  9. hatch/cli_hatch.py +160 -2838
  10. hatch/mcp_host_config/__init__.py +10 -10
  11. hatch/mcp_host_config/adapters/__init__.py +34 -0
  12. hatch/mcp_host_config/adapters/base.py +170 -0
  13. hatch/mcp_host_config/adapters/claude.py +105 -0
  14. hatch/mcp_host_config/adapters/codex.py +104 -0
  15. hatch/mcp_host_config/adapters/cursor.py +83 -0
  16. hatch/mcp_host_config/adapters/gemini.py +75 -0
  17. hatch/mcp_host_config/adapters/kiro.py +78 -0
  18. hatch/mcp_host_config/adapters/lmstudio.py +79 -0
  19. hatch/mcp_host_config/adapters/registry.py +149 -0
  20. hatch/mcp_host_config/adapters/vscode.py +83 -0
  21. hatch/mcp_host_config/backup.py +5 -3
  22. hatch/mcp_host_config/fields.py +126 -0
  23. hatch/mcp_host_config/models.py +161 -456
  24. hatch/mcp_host_config/reporting.py +57 -16
  25. hatch/mcp_host_config/strategies.py +155 -87
  26. hatch/template_generator.py +1 -1
  27. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/METADATA +3 -2
  28. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/RECORD +52 -43
  29. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/WHEEL +1 -1
  30. hatch_xclam-0.8.0.dev1.dist-info/entry_points.txt +2 -0
  31. tests/cli_test_utils.py +280 -0
  32. tests/integration/cli/__init__.py +14 -0
  33. tests/integration/cli/test_cli_reporter_integration.py +2439 -0
  34. tests/integration/mcp/__init__.py +0 -0
  35. tests/integration/mcp/test_adapter_serialization.py +173 -0
  36. tests/regression/cli/__init__.py +16 -0
  37. tests/regression/cli/test_color_logic.py +268 -0
  38. tests/regression/cli/test_consequence_type.py +298 -0
  39. tests/regression/cli/test_error_formatting.py +328 -0
  40. tests/regression/cli/test_result_reporter.py +586 -0
  41. tests/regression/cli/test_table_formatter.py +211 -0
  42. tests/regression/mcp/__init__.py +0 -0
  43. tests/regression/mcp/test_field_filtering.py +162 -0
  44. tests/test_cli_version.py +7 -5
  45. tests/test_data/fixtures/cli_reporter_fixtures.py +184 -0
  46. tests/unit/__init__.py +0 -0
  47. tests/unit/mcp/__init__.py +0 -0
  48. tests/unit/mcp/test_adapter_protocol.py +138 -0
  49. tests/unit/mcp/test_adapter_registry.py +158 -0
  50. tests/unit/mcp/test_config_model.py +146 -0
  51. hatch_xclam-0.7.1.dev3.dist-info/entry_points.txt +0 -2
  52. tests/integration/test_mcp_kiro_integration.py +0 -153
  53. tests/regression/test_mcp_codex_backup_integration.py +0 -162
  54. tests/regression/test_mcp_codex_host_strategy.py +0 -163
  55. tests/regression/test_mcp_codex_model_validation.py +0 -117
  56. tests/regression/test_mcp_kiro_backup_integration.py +0 -241
  57. tests/regression/test_mcp_kiro_cli_integration.py +0 -141
  58. tests/regression/test_mcp_kiro_decorator_registration.py +0 -71
  59. tests/regression/test_mcp_kiro_host_strategy.py +0 -214
  60. tests/regression/test_mcp_kiro_model_validation.py +0 -116
  61. tests/regression/test_mcp_kiro_omni_conversion.py +0 -104
  62. tests/test_mcp_atomic_operations.py +0 -276
  63. tests/test_mcp_backup_integration.py +0 -308
  64. tests/test_mcp_cli_all_host_specific_args.py +0 -496
  65. tests/test_mcp_cli_backup_management.py +0 -295
  66. tests/test_mcp_cli_direct_management.py +0 -456
  67. tests/test_mcp_cli_discovery_listing.py +0 -582
  68. tests/test_mcp_cli_host_config_integration.py +0 -823
  69. tests/test_mcp_cli_package_management.py +0 -360
  70. tests/test_mcp_cli_partial_updates.py +0 -859
  71. tests/test_mcp_environment_integration.py +0 -520
  72. tests/test_mcp_host_config_backup.py +0 -257
  73. tests/test_mcp_host_configuration_manager.py +0 -331
  74. tests/test_mcp_host_registry_decorator.py +0 -348
  75. tests/test_mcp_pydantic_architecture_v4.py +0 -603
  76. tests/test_mcp_server_config_models.py +0 -242
  77. tests/test_mcp_server_config_type_field.py +0 -221
  78. tests/test_mcp_sync_functionality.py +0 -316
  79. tests/test_mcp_user_feedback_reporting.py +0 -359
  80. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/licenses/LICENSE +0 -0
  81. {hatch_xclam-0.7.1.dev3.dist-info → hatch_xclam-0.8.0.dev1.dist-info}/top_level.txt +0 -0
@@ -1,360 +0,0 @@
1
- """
2
- Test suite for MCP CLI package management enhancements.
3
-
4
- This module tests the enhanced package management commands with MCP host
5
- configuration integration following CrackingShells testing standards.
6
- """
7
-
8
- import sys
9
- import unittest
10
- from pathlib import Path
11
- from unittest.mock import MagicMock, mock_open, patch
12
-
13
- # Add the parent directory to the path to import wobble
14
- sys.path.insert(0, str(Path(__file__).parent.parent))
15
-
16
- try:
17
- from wobble.decorators import integration_test, regression_test
18
- except ImportError:
19
- # Fallback decorators if wobble is not available
20
- def regression_test(func):
21
- return func
22
-
23
- def integration_test(scope="component"):
24
- def decorator(func):
25
- return func
26
-
27
- return decorator
28
-
29
-
30
- from hatch.cli_hatch import (
31
- get_package_mcp_server_config,
32
- parse_host_list,
33
- request_confirmation,
34
- )
35
- from hatch.mcp_host_config import MCPHostType, MCPServerConfig
36
-
37
-
38
- class TestMCPCLIPackageManagement(unittest.TestCase):
39
- """Test suite for MCP CLI package management enhancements."""
40
-
41
- @regression_test
42
- def test_parse_host_list_comma_separated(self):
43
- """Test parsing comma-separated host list."""
44
- hosts = parse_host_list("claude-desktop,cursor,vscode")
45
- expected = [MCPHostType.CLAUDE_DESKTOP, MCPHostType.CURSOR, MCPHostType.VSCODE]
46
- self.assertEqual(hosts, expected)
47
-
48
- @regression_test
49
- def test_parse_host_list_single_host(self):
50
- """Test parsing single host."""
51
- hosts = parse_host_list("claude-desktop")
52
- expected = [MCPHostType.CLAUDE_DESKTOP]
53
- self.assertEqual(hosts, expected)
54
-
55
- @regression_test
56
- def test_parse_host_list_empty(self):
57
- """Test parsing empty host list."""
58
- hosts = parse_host_list("")
59
- self.assertEqual(hosts, [])
60
-
61
- @regression_test
62
- def test_parse_host_list_none(self):
63
- """Test parsing None host list."""
64
- hosts = parse_host_list(None)
65
- self.assertEqual(hosts, [])
66
-
67
- @regression_test
68
- def test_parse_host_list_all(self):
69
- """Test parsing 'all' host list."""
70
- with patch(
71
- "hatch.cli_hatch.MCPHostRegistry.detect_available_hosts"
72
- ) as mock_detect:
73
- mock_detect.return_value = [MCPHostType.CLAUDE_DESKTOP, MCPHostType.CURSOR]
74
- hosts = parse_host_list("all")
75
- expected = [MCPHostType.CLAUDE_DESKTOP, MCPHostType.CURSOR]
76
- self.assertEqual(hosts, expected)
77
- mock_detect.assert_called_once()
78
-
79
- @regression_test
80
- def test_parse_host_list_invalid_host(self):
81
- """Test parsing invalid host raises ValueError."""
82
- with self.assertRaises(ValueError) as context:
83
- parse_host_list("invalid-host")
84
-
85
- self.assertIn("Unknown host 'invalid-host'", str(context.exception))
86
- self.assertIn("Available:", str(context.exception))
87
-
88
- @regression_test
89
- def test_parse_host_list_mixed_valid_invalid(self):
90
- """Test parsing mixed valid and invalid hosts."""
91
- with self.assertRaises(ValueError) as context:
92
- parse_host_list("claude-desktop,invalid-host,cursor")
93
-
94
- self.assertIn("Unknown host 'invalid-host'", str(context.exception))
95
-
96
- @regression_test
97
- def test_parse_host_list_whitespace_handling(self):
98
- """Test parsing host list with whitespace."""
99
- hosts = parse_host_list(" claude-desktop , cursor , vscode ")
100
- expected = [MCPHostType.CLAUDE_DESKTOP, MCPHostType.CURSOR, MCPHostType.VSCODE]
101
- self.assertEqual(hosts, expected)
102
-
103
- @regression_test
104
- def test_request_confirmation_auto_approve(self):
105
- """Test confirmation with auto-approve flag."""
106
- result = request_confirmation("Test message?", auto_approve=True)
107
- self.assertTrue(result)
108
-
109
- @regression_test
110
- def test_request_confirmation_user_yes(self):
111
- """Test confirmation with user saying yes."""
112
- with patch("builtins.input", return_value="y"):
113
- result = request_confirmation("Test message?", auto_approve=False)
114
- self.assertTrue(result)
115
-
116
- @regression_test
117
- def test_request_confirmation_user_yes_full(self):
118
- """Test confirmation with user saying 'yes'."""
119
- with patch("builtins.input", return_value="yes"):
120
- result = request_confirmation("Test message?", auto_approve=False)
121
- self.assertTrue(result)
122
-
123
- @regression_test
124
- def test_request_confirmation_user_no(self):
125
- """Test confirmation with user saying no."""
126
- with patch.dict("os.environ", {"HATCH_AUTO_APPROVE": ""}, clear=False):
127
- with patch("builtins.input", return_value="n"):
128
- result = request_confirmation("Test message?", auto_approve=False)
129
- self.assertFalse(result)
130
-
131
- @regression_test
132
- def test_request_confirmation_user_no_full(self):
133
- """Test confirmation with user saying 'no'."""
134
- with patch.dict("os.environ", {"HATCH_AUTO_APPROVE": ""}, clear=False):
135
- with patch("builtins.input", return_value="no"):
136
- result = request_confirmation("Test message?", auto_approve=False)
137
- self.assertFalse(result)
138
-
139
- @regression_test
140
- def test_request_confirmation_user_empty(self):
141
- """Test confirmation with user pressing enter (default no)."""
142
- with patch.dict("os.environ", {"HATCH_AUTO_APPROVE": ""}, clear=False):
143
- with patch("builtins.input", return_value=""):
144
- result = request_confirmation("Test message?", auto_approve=False)
145
- self.assertFalse(result)
146
-
147
- @integration_test(scope="component")
148
- def test_package_add_argument_parsing(self):
149
- """Test package add command argument parsing with MCP flags."""
150
- import argparse
151
-
152
- from hatch.cli_hatch import main
153
-
154
- # Mock argparse to capture parsed arguments
155
- with patch("argparse.ArgumentParser.parse_args") as mock_parse:
156
- mock_args = MagicMock()
157
- mock_args.command = "package"
158
- mock_args.pkg_command = "add"
159
- mock_args.package_path_or_name = "test-package"
160
- mock_args.host = "claude-desktop,cursor"
161
- mock_args.env = None
162
- mock_args.version = None
163
- mock_args.force_download = False
164
- mock_args.refresh_registry = False
165
- mock_args.auto_approve = False
166
- mock_parse.return_value = mock_args
167
-
168
- # Mock environment manager to avoid actual operations
169
- with patch("hatch.cli_hatch.HatchEnvironmentManager") as mock_env_manager:
170
- mock_env_manager.return_value.add_package_to_environment.return_value = True
171
- mock_env_manager.return_value.get_current_environment.return_value = (
172
- "default"
173
- )
174
-
175
- # Mock MCP manager
176
- with patch("hatch.cli_hatch.MCPHostConfigurationManager"):
177
- with patch("builtins.print") as mock_print:
178
- result = main()
179
-
180
- # Should succeed
181
- self.assertEqual(result, 0)
182
-
183
- # Should print success message
184
- mock_print.assert_any_call(
185
- "Successfully added package: test-package"
186
- )
187
-
188
- @integration_test(scope="component")
189
- def test_package_sync_argument_parsing(self):
190
- """Test package sync command argument parsing."""
191
- import argparse
192
-
193
- from hatch.cli_hatch import main
194
-
195
- # Mock argparse to capture parsed arguments
196
- with patch("argparse.ArgumentParser.parse_args") as mock_parse:
197
- mock_args = MagicMock()
198
- mock_args.command = "package"
199
- mock_args.pkg_command = "sync"
200
- mock_args.package_name = "test-package"
201
- mock_args.host = "claude-desktop,cursor"
202
- mock_args.env = None
203
- mock_args.dry_run = True # Use dry run to avoid actual configuration
204
- mock_args.auto_approve = False
205
- mock_args.no_backup = False
206
- mock_parse.return_value = mock_args
207
-
208
- # Mock the get_package_mcp_server_config function
209
- with patch(
210
- "hatch.cli_hatch.get_package_mcp_server_config"
211
- ) as mock_get_config:
212
- mock_server_config = MagicMock()
213
- mock_server_config.name = "test-package"
214
- mock_server_config.args = ["/path/to/server.py"]
215
- mock_get_config.return_value = mock_server_config
216
-
217
- # Mock environment manager
218
- with patch(
219
- "hatch.cli_hatch.HatchEnvironmentManager"
220
- ) as mock_env_manager:
221
- mock_env_manager.return_value.get_current_environment.return_value = "default"
222
-
223
- # Mock MCP manager
224
- with patch("hatch.cli_hatch.MCPHostConfigurationManager"):
225
- with patch("builtins.print") as mock_print:
226
- result = main()
227
-
228
- # Should succeed
229
- self.assertEqual(result, 0)
230
-
231
- # Should print dry run message (new format includes dependency info)
232
- mock_print.assert_any_call(
233
- "[DRY RUN] Would synchronize MCP servers for 1 package(s) to hosts: ['claude-desktop', 'cursor']"
234
- )
235
-
236
- @integration_test(scope="component")
237
- def test_package_sync_package_not_found(self):
238
- """Test package sync when package doesn't exist."""
239
- import argparse
240
-
241
- from hatch.cli_hatch import main
242
-
243
- # Mock argparse to capture parsed arguments
244
- with patch("argparse.ArgumentParser.parse_args") as mock_parse:
245
- mock_args = MagicMock()
246
- mock_args.command = "package"
247
- mock_args.pkg_command = "sync"
248
- mock_args.package_name = "nonexistent-package"
249
- mock_args.host = "claude-desktop"
250
- mock_args.env = None
251
- mock_args.dry_run = False
252
- mock_args.auto_approve = False
253
- mock_args.no_backup = False
254
- mock_parse.return_value = mock_args
255
-
256
- # Mock the get_package_mcp_server_config function to raise ValueError
257
- with patch(
258
- "hatch.cli_hatch.get_package_mcp_server_config"
259
- ) as mock_get_config:
260
- mock_get_config.side_effect = ValueError(
261
- "Package 'nonexistent-package' not found in environment 'default'"
262
- )
263
-
264
- # Mock environment manager
265
- with patch(
266
- "hatch.cli_hatch.HatchEnvironmentManager"
267
- ) as mock_env_manager:
268
- mock_env_manager.return_value.get_current_environment.return_value = "default"
269
-
270
- with patch("builtins.print") as mock_print:
271
- result = main()
272
-
273
- # Should fail
274
- self.assertEqual(result, 1)
275
-
276
- # Should print error message (new format)
277
- mock_print.assert_any_call(
278
- "Error: No MCP server configurations found for package 'nonexistent-package' or its dependencies"
279
- )
280
-
281
- @regression_test
282
- def test_get_package_mcp_server_config_success(self):
283
- """Test successful MCP server config retrieval."""
284
- # Mock environment manager
285
- mock_env_manager = MagicMock()
286
- mock_env_manager.list_packages.return_value = [
287
- {
288
- "name": "test-package",
289
- "version": "1.0.0",
290
- "source": {"path": "/path/to/package"},
291
- }
292
- ]
293
- # Mock the Python executable method to return a proper string
294
- mock_env_manager.get_current_python_executable.return_value = "/path/to/python"
295
-
296
- # Mock file system and metadata
297
- with patch("pathlib.Path.exists", return_value=True):
298
- with patch(
299
- "builtins.open",
300
- mock_open(
301
- read_data='{"package_schema_version": "1.2.1", "name": "test-package"}'
302
- ),
303
- ):
304
- with patch(
305
- "hatch_validator.package.package_service.PackageService"
306
- ) as mock_service_class:
307
- mock_service = MagicMock()
308
- mock_service.get_mcp_entry_point.return_value = "mcp_server.py"
309
- mock_service_class.return_value = mock_service
310
-
311
- config = get_package_mcp_server_config(
312
- mock_env_manager, "test-env", "test-package"
313
- )
314
-
315
- self.assertIsInstance(config, MCPServerConfig)
316
- self.assertEqual(config.name, "test-package")
317
- self.assertEqual(
318
- config.command, "/path/to/python"
319
- ) # Now uses environment-specific Python
320
- self.assertTrue(config.args[0].endswith("mcp_server.py"))
321
-
322
- @regression_test
323
- def test_get_package_mcp_server_config_package_not_found(self):
324
- """Test MCP server config retrieval when package not found."""
325
- # Mock environment manager with empty package list
326
- mock_env_manager = MagicMock()
327
- mock_env_manager.list_packages.return_value = []
328
-
329
- with self.assertRaises(ValueError) as context:
330
- get_package_mcp_server_config(
331
- mock_env_manager, "test-env", "nonexistent-package"
332
- )
333
-
334
- self.assertIn("Package 'nonexistent-package' not found", str(context.exception))
335
-
336
- @regression_test
337
- def test_get_package_mcp_server_config_no_metadata(self):
338
- """Test MCP server config retrieval when package has no metadata."""
339
- # Mock environment manager
340
- mock_env_manager = MagicMock()
341
- mock_env_manager.list_packages.return_value = [
342
- {
343
- "name": "test-package",
344
- "version": "1.0.0",
345
- "source": {"path": "/path/to/package"},
346
- }
347
- ]
348
-
349
- # Mock file system - metadata file doesn't exist
350
- with patch("pathlib.Path.exists", return_value=False):
351
- with self.assertRaises(ValueError) as context:
352
- get_package_mcp_server_config(
353
- mock_env_manager, "test-env", "test-package"
354
- )
355
-
356
- self.assertIn("not a Hatch package", str(context.exception))
357
-
358
-
359
- if __name__ == "__main__":
360
- unittest.main()