foundry-mcp 0.3.3__py3-none-any.whl → 0.8.10__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 (85) hide show
  1. foundry_mcp/__init__.py +7 -1
  2. foundry_mcp/cli/__init__.py +0 -13
  3. foundry_mcp/cli/commands/plan.py +10 -3
  4. foundry_mcp/cli/commands/review.py +19 -4
  5. foundry_mcp/cli/commands/session.py +1 -8
  6. foundry_mcp/cli/commands/specs.py +38 -208
  7. foundry_mcp/cli/context.py +39 -0
  8. foundry_mcp/cli/output.py +3 -3
  9. foundry_mcp/config.py +615 -11
  10. foundry_mcp/core/ai_consultation.py +146 -9
  11. foundry_mcp/core/batch_operations.py +1196 -0
  12. foundry_mcp/core/discovery.py +7 -7
  13. foundry_mcp/core/error_store.py +2 -2
  14. foundry_mcp/core/intake.py +933 -0
  15. foundry_mcp/core/llm_config.py +28 -2
  16. foundry_mcp/core/metrics_store.py +2 -2
  17. foundry_mcp/core/naming.py +25 -2
  18. foundry_mcp/core/progress.py +70 -0
  19. foundry_mcp/core/prometheus.py +0 -13
  20. foundry_mcp/core/prompts/fidelity_review.py +149 -4
  21. foundry_mcp/core/prompts/markdown_plan_review.py +5 -1
  22. foundry_mcp/core/prompts/plan_review.py +5 -1
  23. foundry_mcp/core/providers/__init__.py +12 -0
  24. foundry_mcp/core/providers/base.py +39 -0
  25. foundry_mcp/core/providers/claude.py +51 -48
  26. foundry_mcp/core/providers/codex.py +70 -60
  27. foundry_mcp/core/providers/cursor_agent.py +25 -47
  28. foundry_mcp/core/providers/detectors.py +34 -7
  29. foundry_mcp/core/providers/gemini.py +69 -58
  30. foundry_mcp/core/providers/opencode.py +101 -47
  31. foundry_mcp/core/providers/package-lock.json +4 -4
  32. foundry_mcp/core/providers/package.json +1 -1
  33. foundry_mcp/core/providers/validation.py +128 -0
  34. foundry_mcp/core/research/__init__.py +68 -0
  35. foundry_mcp/core/research/memory.py +528 -0
  36. foundry_mcp/core/research/models.py +1220 -0
  37. foundry_mcp/core/research/providers/__init__.py +40 -0
  38. foundry_mcp/core/research/providers/base.py +242 -0
  39. foundry_mcp/core/research/providers/google.py +507 -0
  40. foundry_mcp/core/research/providers/perplexity.py +442 -0
  41. foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
  42. foundry_mcp/core/research/providers/tavily.py +383 -0
  43. foundry_mcp/core/research/workflows/__init__.py +25 -0
  44. foundry_mcp/core/research/workflows/base.py +298 -0
  45. foundry_mcp/core/research/workflows/chat.py +271 -0
  46. foundry_mcp/core/research/workflows/consensus.py +539 -0
  47. foundry_mcp/core/research/workflows/deep_research.py +4020 -0
  48. foundry_mcp/core/research/workflows/ideate.py +682 -0
  49. foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
  50. foundry_mcp/core/responses.py +690 -0
  51. foundry_mcp/core/spec.py +2439 -236
  52. foundry_mcp/core/task.py +1205 -31
  53. foundry_mcp/core/testing.py +512 -123
  54. foundry_mcp/core/validation.py +319 -43
  55. foundry_mcp/dashboard/components/charts.py +0 -57
  56. foundry_mcp/dashboard/launcher.py +11 -0
  57. foundry_mcp/dashboard/views/metrics.py +25 -35
  58. foundry_mcp/dashboard/views/overview.py +1 -65
  59. foundry_mcp/resources/specs.py +25 -25
  60. foundry_mcp/schemas/intake-schema.json +89 -0
  61. foundry_mcp/schemas/sdd-spec-schema.json +33 -5
  62. foundry_mcp/server.py +0 -14
  63. foundry_mcp/tools/unified/__init__.py +39 -18
  64. foundry_mcp/tools/unified/authoring.py +2371 -248
  65. foundry_mcp/tools/unified/documentation_helpers.py +69 -6
  66. foundry_mcp/tools/unified/environment.py +434 -32
  67. foundry_mcp/tools/unified/error.py +18 -1
  68. foundry_mcp/tools/unified/lifecycle.py +8 -0
  69. foundry_mcp/tools/unified/plan.py +133 -2
  70. foundry_mcp/tools/unified/provider.py +0 -40
  71. foundry_mcp/tools/unified/research.py +1283 -0
  72. foundry_mcp/tools/unified/review.py +374 -17
  73. foundry_mcp/tools/unified/review_helpers.py +16 -1
  74. foundry_mcp/tools/unified/server.py +9 -24
  75. foundry_mcp/tools/unified/spec.py +367 -0
  76. foundry_mcp/tools/unified/task.py +1664 -30
  77. foundry_mcp/tools/unified/test.py +69 -8
  78. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/METADATA +8 -1
  79. foundry_mcp-0.8.10.dist-info/RECORD +153 -0
  80. foundry_mcp/cli/flags.py +0 -266
  81. foundry_mcp/core/feature_flags.py +0 -592
  82. foundry_mcp-0.3.3.dist-info/RECORD +0 -135
  83. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/WHEEL +0 -0
  84. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/entry_points.txt +0 -0
  85. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/licenses/LICENSE +0 -0
@@ -23,7 +23,12 @@ from foundry_mcp.core.responses import (
23
23
  error_response,
24
24
  success_response,
25
25
  )
26
- from foundry_mcp.core.testing import TestRunner, get_presets
26
+ from foundry_mcp.core.testing import (
27
+ TestRunner,
28
+ get_presets,
29
+ get_runner,
30
+ get_available_runners,
31
+ )
27
32
  from foundry_mcp.tools.unified.router import (
28
33
  ActionDefinition,
29
34
  ActionRouter,
@@ -42,13 +47,31 @@ def _metric(action: str) -> str:
42
47
  return f"unified_tools.test.{action.replace('-', '_')}"
43
48
 
44
49
 
45
- def _get_runner(config: ServerConfig, workspace: Optional[str]) -> TestRunner:
50
+ def _get_test_runner(
51
+ config: ServerConfig,
52
+ workspace: Optional[str],
53
+ runner_name: Optional[str] = None,
54
+ ) -> TestRunner:
55
+ """Get a TestRunner with the appropriate backend.
56
+
57
+ Args:
58
+ config: Server configuration
59
+ workspace: Workspace path override
60
+ runner_name: Name of the test runner backend to use
61
+
62
+ Returns:
63
+ TestRunner configured with the appropriate backend
64
+ """
46
65
  ws: Optional[Path] = None
47
66
  if workspace:
48
67
  ws = Path(workspace)
49
68
  elif config.specs_dir is not None:
50
69
  ws = config.specs_dir.parent
51
- return TestRunner(workspace=ws)
70
+
71
+ # Get the runner backend from config or defaults
72
+ runner_backend = get_runner(runner_name, config.test)
73
+
74
+ return TestRunner(workspace=ws, runner=runner_backend)
52
75
 
53
76
 
54
77
  def _validation_error(
@@ -68,6 +91,24 @@ def _validation_error(
68
91
  def _handle_run(*, config: ServerConfig, payload: Dict[str, Any]) -> dict:
69
92
  request_id = _request_id()
70
93
 
94
+ # Validate runner parameter
95
+ runner_name = payload.get("runner")
96
+ if runner_name is not None and not isinstance(runner_name, str):
97
+ return _validation_error(
98
+ message="runner must be a string",
99
+ request_id=request_id,
100
+ remediation="Use runner=pytest|go|npm|jest|make or a custom runner name",
101
+ )
102
+
103
+ if isinstance(runner_name, str):
104
+ available_runners = get_available_runners(config.test)
105
+ if runner_name not in available_runners:
106
+ return _validation_error(
107
+ message=f"Unknown runner: {runner_name}",
108
+ request_id=request_id,
109
+ remediation=f"Use one of: {', '.join(sorted(available_runners))}",
110
+ )
111
+
71
112
  preset = payload.get("preset")
72
113
  if preset is not None and not isinstance(preset, str):
73
114
  return _validation_error(
@@ -90,7 +131,7 @@ def _handle_run(*, config: ServerConfig, payload: Dict[str, Any]) -> dict:
90
131
  return _validation_error(
91
132
  message="target must be a string",
92
133
  request_id=request_id,
93
- remediation="Provide a pytest target like tests/unit or tests/test_file.py",
134
+ remediation="Provide a test target like tests/unit or tests/test_file.py",
94
135
  )
95
136
 
96
137
  timeout = payload.get("timeout", 300)
@@ -155,7 +196,7 @@ def _handle_run(*, config: ServerConfig, payload: Dict[str, Any]) -> dict:
155
196
  include_passed_value if isinstance(include_passed_value, bool) else False
156
197
  )
157
198
 
158
- runner = _get_runner(config, workspace)
199
+ runner = _get_test_runner(config, workspace, runner_name)
159
200
 
160
201
  start = time.perf_counter()
161
202
  result = runner.run_tests(
@@ -223,6 +264,24 @@ def _handle_run(*, config: ServerConfig, payload: Dict[str, Any]) -> dict:
223
264
  def _handle_discover(*, config: ServerConfig, payload: Dict[str, Any]) -> dict:
224
265
  request_id = _request_id()
225
266
 
267
+ # Validate runner parameter
268
+ runner_name = payload.get("runner")
269
+ if runner_name is not None and not isinstance(runner_name, str):
270
+ return _validation_error(
271
+ message="runner must be a string",
272
+ request_id=request_id,
273
+ remediation="Use runner=pytest|go|npm|jest|make or a custom runner name",
274
+ )
275
+
276
+ if isinstance(runner_name, str):
277
+ available_runners = get_available_runners(config.test)
278
+ if runner_name not in available_runners:
279
+ return _validation_error(
280
+ message=f"Unknown runner: {runner_name}",
281
+ request_id=request_id,
282
+ remediation=f"Use one of: {', '.join(sorted(available_runners))}",
283
+ )
284
+
226
285
  target = payload.get("target")
227
286
  if target is not None and not isinstance(target, str):
228
287
  return _validation_error(
@@ -247,7 +306,7 @@ def _handle_discover(*, config: ServerConfig, payload: Dict[str, Any]) -> dict:
247
306
  remediation="Provide an absolute path to the workspace",
248
307
  )
249
308
 
250
- runner = _get_runner(config, workspace)
309
+ runner = _get_test_runner(config, workspace, runner_name)
251
310
 
252
311
  start = time.perf_counter()
253
312
  result = runner.discover_tests(target=target, pattern=pattern)
@@ -291,8 +350,8 @@ def _handle_discover(*, config: ServerConfig, payload: Dict[str, Any]) -> dict:
291
350
 
292
351
 
293
352
  _ACTION_SUMMARY = {
294
- "run": "Execute pytest using a preset or target.",
295
- "discover": "Discover pytest tests without executing.",
353
+ "run": "Execute tests using the specified runner (pytest, go, npm, jest, make).",
354
+ "discover": "Discover tests without executing using the specified runner.",
296
355
  }
297
356
 
298
357
 
@@ -341,6 +400,7 @@ def register_unified_test_tool(mcp: FastMCP, config: ServerConfig) -> None:
341
400
  action: str,
342
401
  target: Optional[str] = None,
343
402
  preset: Optional[str] = None,
403
+ runner: Optional[str] = None,
344
404
  timeout: int = 300,
345
405
  verbose: bool = True,
346
406
  fail_fast: bool = False,
@@ -352,6 +412,7 @@ def register_unified_test_tool(mcp: FastMCP, config: ServerConfig) -> None:
352
412
  payload: Dict[str, Any] = {
353
413
  "target": target,
354
414
  "preset": preset,
415
+ "runner": runner,
355
416
  "timeout": timeout,
356
417
  "verbose": verbose,
357
418
  "fail_fast": fail_fast,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: foundry-mcp
3
- Version: 0.3.3
3
+ Version: 0.8.10
4
4
  Summary: MCP server for SDD toolkit spec management
5
5
  Project-URL: Homepage, https://github.com/tylerburleigh/foundry-mcp
6
6
  Project-URL: Repository, https://github.com/tylerburleigh/foundry-mcp
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Requires-Python: >=3.10
19
19
  Requires-Dist: click>=8.0.0
20
20
  Requires-Dist: fastmcp>=0.1.0
21
+ Requires-Dist: filelock>=3.20.1
21
22
  Requires-Dist: mcp>=1.0.0
22
23
  Requires-Dist: tomli>=2.0.0; python_version < '3.11'
23
24
  Provides-Extra: dashboard
@@ -125,6 +126,11 @@ specs/
125
126
  - `plan(action=create|list|review)` supports lightweight planning and review flows.
126
127
  - Notifications and sampling channels surface phase completions to MCP clients.
127
128
 
129
+ ### Batch metadata utilities
130
+
131
+ - `task(action=metadata-batch)` — Apply metadata updates (e.g., `file_path`, `estimated_hours`) to multiple nodes at once. Supports flexible AND-based filtering by `node_type`, `phase_id`, or `pattern` regex. Includes `dry_run` mode for previewing changes.
132
+ - `task(action=fix-verification-types)` — Auto-fix invalid or missing `verification_type` on verify nodes. Supports legacy mappings (`test` → `run-tests`, `auto` → `run-tests`) and defaults unknown types to `manual`. Includes `dry_run` mode for previewing fixes.
133
+
128
134
  ### Code, docs, and testing intelligence
129
135
 
130
136
  - Code navigation tools via `code(action=...)` support symbol lookup and call-graph tracing.
@@ -199,6 +205,7 @@ All MCP tools emit the standardized envelope defined in `docs/codebase_standards
199
205
  | `FOUNDRY_MCP_API_KEYS` | Comma-separated API keys required for tool access | Disabled |
200
206
  | `FOUNDRY_MCP_FEATURE_FLAGS` | Additional feature flags to enable (e.g., `planning_tools`) | Based on spec rollout |
201
207
  | `FOUNDRY_MCP_RESPONSE_CONTRACT` | Force response contract version (`v2`) | Auto-negotiated |
208
+ | `FOUNDRY_MODE` | Server mode: `full` (16 tools) or `minimal` (1 wake tool) | `full` |
202
209
  | `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` | LLM provider credentials | Not set |
203
210
 
204
211
  ### TOML configuration
@@ -0,0 +1,153 @@
1
+ foundry_mcp/__init__.py,sha256=r2Q-D1qsSPgSgUM53YV2Se_yyIZRwYKluJJKCEjEAKQ,404
2
+ foundry_mcp/config.py,sha256=G7SsxFN-hkqR2QdL5fLp0tONYfyJd7JI5nWUQL-YXTA,57002
3
+ foundry_mcp/server.py,sha256=9GjG4xkk2yD_nNcynCC8RV0EX7xljRWeoWQ-mC0COaw,4897
4
+ foundry_mcp/cli/__init__.py,sha256=K7cSTO88RGrsSwQkpZvhi88ZuazN8X_ldPDEhnAGyqU,1583
5
+ foundry_mcp/cli/__main__.py,sha256=wPwlid-SomQl5oGxtM319jjN4OSE-KWZtsdHctp4juU,167
6
+ foundry_mcp/cli/agent.py,sha256=QPuoZkLeNwmvjqCb2a3CPXJx9YWNJmd8s4a9iRbENIM,2855
7
+ foundry_mcp/cli/config.py,sha256=9jdpVw_LfTOOLR9LO3wzm82ziwKq7tv1JMBrj40iWsE,2980
8
+ foundry_mcp/cli/context.py,sha256=RKlYfcDsjS3Z-rvQxQl3DT8FjDQliDi928SCTJR28MI,10480
9
+ foundry_mcp/cli/logging.py,sha256=z-O-qQSIfndcIOiCkKr8TJ78Pldn0jNXWtjrCk8t1Ho,5974
10
+ foundry_mcp/cli/main.py,sha256=0BPbEbbNzZtS1rDjTqQlLZw2cGL4jaOdHtSk5ov9NWU,1088
11
+ foundry_mcp/cli/output.py,sha256=ZH3cHMoNo39hEXlXO2hcdX21SZdg5ewXyV7YGjl1txs,3986
12
+ foundry_mcp/cli/registry.py,sha256=25yV3o-q7VgnfqjDvqVQHyphe9BQF6y2y70zY52jWLM,2816
13
+ foundry_mcp/cli/resilience.py,sha256=o4flW5JN9nOPq5RVYvRf88cEb4KbqgI3kg2rfkcxv6g,5248
14
+ foundry_mcp/cli/transcript.py,sha256=DbusgsUCipi9l8YVHY20U5rbE13dM76PQbUokGocgV4,7227
15
+ foundry_mcp/cli/commands/__init__.py,sha256=vU8zLcC1opoM5Ox4du-0mG4gxHJ6N1tRaE4F0KoJBfk,1171
16
+ foundry_mcp/cli/commands/cache.py,sha256=_bVPkWXRT4Q-EtDOQIYY6TkJR1m49dm4RQ_lbSEaqRE,3373
17
+ foundry_mcp/cli/commands/dashboard.py,sha256=9T_Lu78p9y491_njoPgM02uPBccp11jbDNBJ1IttGUU,3868
18
+ foundry_mcp/cli/commands/dev.py,sha256=kBtZacvf-lXyGpmdNuZIZZrdZdtMEtxsQqP6HtUR5mk,12210
19
+ foundry_mcp/cli/commands/journal.py,sha256=T7SGvZrdnV0iN1TgHibqBoZSVSMWaye7FiFED3duYik,10936
20
+ foundry_mcp/cli/commands/lifecycle.py,sha256=vtmNyw4QL1igwEeQMoyM0lD-blCvY-3qlYF6sZHc4Z8,8301
21
+ foundry_mcp/cli/commands/modify.py,sha256=Loe5J-P093E9rFJJDZyRFnQB6ez6OkWlZ_eF1bVmiC0,21936
22
+ foundry_mcp/cli/commands/plan.py,sha256=T5V2Kb0GUcypBpAHrVCzvfp9QrGyn3UAwQ3IeP3OZg4,18017
23
+ foundry_mcp/cli/commands/pr.py,sha256=4PnLJrsB7op6RIP7Z1cDvrcSGMfknRRfBJrDd8aXBpw,11035
24
+ foundry_mcp/cli/commands/review.py,sha256=2AblIo2NgXHEghlKLFGYeMRnj27mw_mTvjxRUpjHyBo,21174
25
+ foundry_mcp/cli/commands/session.py,sha256=ZBWNHblgqkQHKkx4hkFZIQ5K1Wh7LnB8vw0nMdXtenw,15700
26
+ foundry_mcp/cli/commands/specs.py,sha256=awVhJ1hUvDai4IibjEjDDUcB_FOmSTo-yLh7yROF6h8,20644
27
+ foundry_mcp/cli/commands/tasks.py,sha256=TToK6pcwlAqF1zYQ2mKB36Esw9C3htAyvx6Qc8-nOQE,25246
28
+ foundry_mcp/cli/commands/testing.py,sha256=5ZtZ2XJ79V9FmsnvgQ1Z47qGGKMVeFuCOf7GivntMkI,20317
29
+ foundry_mcp/cli/commands/validate.py,sha256=Y_dJmx0WgdxYQkIa8y6H0HSHtQBhXYaqFMv62wEwuD8,30630
30
+ foundry_mcp/core/__init__.py,sha256=LgBtknZIRhvDQT2c8D07TeymkSmDZonrCxWZUZOgeSM,3025
31
+ foundry_mcp/core/ai_consultation.py,sha256=wAtIUuCHIGy84Bbv6WIC4nEg91x5VqjOH1w58y7zTeA,62861
32
+ foundry_mcp/core/batch_operations.py,sha256=8kNDN-Xw9lXzcV4a1K3oGK45SV-4bAW16S97xb5Cduc,38179
33
+ foundry_mcp/core/cache.py,sha256=7eM1-J_CXgQUid1JTTRS0aRxOZK9HgTRLEzX-k534po,5443
34
+ foundry_mcp/core/capabilities.py,sha256=Zq6QJ7bvHL4GIOxjQNjmUGLFBeEjvVNj4XP2RLswebE,13144
35
+ foundry_mcp/core/concurrency.py,sha256=qOlfYmlSDYU-deqnqa0r5ccGwey-MIM5QiDag_K6xzk,26571
36
+ foundry_mcp/core/context.py,sha256=2M77uy-1KW-EzVxT2bdWPUFc6mWbANK1iYsCNFhMOls,16292
37
+ foundry_mcp/core/discovery.py,sha256=d-pouSFhYQToQnH6s6Yw5cPDHKp2UpRzMkkNhlmHaKs,53898
38
+ foundry_mcp/core/error_collection.py,sha256=MjpYU0OrTpQRIg8ND-EQzd-H-vM4LKtRHUNuveQFvp8,25069
39
+ foundry_mcp/core/error_store.py,sha256=P5irmUJIXh9BNyg_Mh7HYoKqtSjwHpj9vhUcwtqh4V8,20725
40
+ foundry_mcp/core/health.py,sha256=GcpLnHDSF5W_nf0k7-5icGeZmrKw-sUU_67ddMe8Kt4,25115
41
+ foundry_mcp/core/intake.py,sha256=qjmX5gk9Ae5HjSVLvwmQmPSloJFMsho1SsyUboNhkx8,32722
42
+ foundry_mcp/core/journal.py,sha256=f1q52bjVOOLqMevd4hmdii2vCeLRT-sQhEwaUj0s9Bk,19124
43
+ foundry_mcp/core/lifecycle.py,sha256=LALbkljjUrXtGe0No8eux9vAr-AIlDDltn9jz1HToHY,10959
44
+ foundry_mcp/core/llm_config.py,sha256=9wzUiT7-oAXE-T__VNwX812jUtS6avqczgdBELjCgis,48489
45
+ foundry_mcp/core/llm_patterns.py,sha256=1ZCuCkZ6ov2abk4Wt6T35a2Fg0Dp-0ALV5sdm5N9gXE,15336
46
+ foundry_mcp/core/llm_provider.py,sha256=VG4lJn5gIF8QNbAV81ysyEPkbg3XehCdw5m2KDtae9c,52595
47
+ foundry_mcp/core/logging_config.py,sha256=Dlo6lL5eollUXk9U43PviuvU9d6jW_AREEQK63vE2IE,10960
48
+ foundry_mcp/core/metrics_persistence.py,sha256=rvcNsaGXC811feicjLu7nq7ayUX_aTD7tyHcDGPYO8w,18231
49
+ foundry_mcp/core/metrics_registry.py,sha256=TpOkaFXpPMDagV537IbNPW4InZH1IaQT8v0pLZyBXJc,10438
50
+ foundry_mcp/core/metrics_store.py,sha256=ctBgaedVkSmeyQlQmkrnJEg9jAAxw6sZNfCW2xt2a6o,22482
51
+ foundry_mcp/core/modifications.py,sha256=g6nnXunEhqjXyr5HrV3dPoMMmMjML-3tYWKjlmxt1GY,6343
52
+ foundry_mcp/core/naming.py,sha256=lzIx_6ZScAHD2RO_N_FOzLDoKS7J2Drxh6irCE083IY,4779
53
+ foundry_mcp/core/observability.py,sha256=Y7f9ws8Glml4ThoMynj4G0vkhZEAw2rHtglw9uap7Vo,41477
54
+ foundry_mcp/core/otel.py,sha256=BVb4rgyo11GMUnJuGSji_0mjvTNyO8f5E1LNCoyVe08,13516
55
+ foundry_mcp/core/otel_stubs.py,sha256=lVWL7qQHo7QRj9NvvyqNvvxzjGvLFiDEdJQDqpc5XcE,6834
56
+ foundry_mcp/core/pagination.py,sha256=S_iTmZYmLda1to-__LLMM0sQI1xJMvAzjW0NAHLKx6A,7243
57
+ foundry_mcp/core/progress.py,sha256=ZFBVzxFY_YTQPKOM1zOA_ylQG-tJishht3h41xhWz2I,11978
58
+ foundry_mcp/core/prometheus.py,sha256=d2pYrRlY201RCzMPG_70wrJ0PzCdVzBbJqFAJ7_IiOw,17313
59
+ foundry_mcp/core/rate_limit.py,sha256=6Hrx60Az4z8lxXW0DeEx1hrGQPXRwbhhRVI1PA72BKQ,13395
60
+ foundry_mcp/core/resilience.py,sha256=DrH6fbMYXjRmjKDukT8zLL49JEKsgza2LGsgBow4usQ,18831
61
+ foundry_mcp/core/responses.py,sha256=q9OTtuWzL5ueKT3iEGKa6HTMTYDLM-PsXA_G6jbmyHI,55048
62
+ foundry_mcp/core/review.py,sha256=DkiNG1DWgmm5PCuZrGstYI6mcd7ub9sXQpPARbk-HWM,11183
63
+ foundry_mcp/core/security.py,sha256=4ABOhWBzu7P9gMuC8gTXr3PrjEmEp_eSAgrFZG2sjyI,15444
64
+ foundry_mcp/core/spec.py,sha256=KsEJE9iftWozXIxkcIYZaKKrj1LkEh7S-7bz0N4HE5s,129379
65
+ foundry_mcp/core/task.py,sha256=CCt_qJD-60Z_r5FmZfUlLVGpdQrpUT6WpVe_PohXsRg,85886
66
+ foundry_mcp/core/testing.py,sha256=roee_ZddoB7xNxRk1pz89-4eOb6Sc2X6vKk0VLzd3Cg,24930
67
+ foundry_mcp/core/validation.py,sha256=Ra5SYGk5iULxBMDqXRQRYFQgrwRfCAv1Y4yqlASbl4Y,80887
68
+ foundry_mcp/core/prompts/__init__.py,sha256=0rx9sX-D_Zgfah-gPoUPYxW5GnhjxZmCVNzvurMOk8s,14316
69
+ foundry_mcp/core/prompts/fidelity_review.py,sha256=mmuWuDW5Ljj1IUYy3yGY7MjuOguLhytz5tYY9y0SqOI,22360
70
+ foundry_mcp/core/prompts/markdown_plan_review.py,sha256=WXH0N-QwC31Bo4i2lrCGWfIjviyi0ZkPS5kAuNOYsZ0,18256
71
+ foundry_mcp/core/prompts/plan_review.py,sha256=m4N5p_EQFrtvhYxt5PVtU9KcSr9sDBGboF-9ZvR40Cg,21992
72
+ foundry_mcp/core/providers/__init__.py,sha256=E9PuavQrH9LyjP33PWwIlBo2uYjjbOebqpvm0N42E7o,6286
73
+ foundry_mcp/core/providers/base.py,sha256=9tNNY4R-mFmiLEocqrDk5Lgt9iSVAd9BZWac0qJ7tXY,17953
74
+ foundry_mcp/core/providers/claude.py,sha256=8lEQ-HXCshMuvsezcHClL3eTSGpMnCW_MFGLkDXpYdM,15108
75
+ foundry_mcp/core/providers/codex.py,sha256=uKzazf1Xt55Yt4zldIQnJBHbR7pNarpMi-83D4wOgKo,21374
76
+ foundry_mcp/core/providers/cursor_agent.py,sha256=wo71-4NzgQJQRveNRhGHI4uq5QxZbCOJ01cnwQzqyHs,22101
77
+ foundry_mcp/core/providers/detectors.py,sha256=yOEUB22-P11zdxhNXVOxr-R435YQ4PGx81RmF8vqHSo,16580
78
+ foundry_mcp/core/providers/gemini.py,sha256=1w5pzdGKzDPv1zy9ZJS6pO4c6PB7cLrxILNqIFbGcvw,14283
79
+ foundry_mcp/core/providers/opencode.py,sha256=XCj3I65hvIARet10GL6CJAHOBK_3lz0A_5drn07E-UY,24040
80
+ foundry_mcp/core/providers/opencode_wrapper.js,sha256=m7zHGmXwYGM8SQn29Wq3o8rVUa-gOLXq_haPwXHDI4w,7521
81
+ foundry_mcp/core/providers/package-lock.json,sha256=J49dNHIagU2eArW1wyZhGqHaHOV6EyiNerdldoPF3Qs,648
82
+ foundry_mcp/core/providers/package.json,sha256=nwRGNP00W0_kSgSN4b8BxofmagXYPTELhNm3p_eg3g8,513
83
+ foundry_mcp/core/providers/registry.py,sha256=uAVJ0dvE_lTtTM6lE_RV9QCvGjP33gYxxXYCy_68Y8c,19147
84
+ foundry_mcp/core/providers/test_provider.py,sha256=7RlAkc8H-2uBd25tmKs_Cu7Fl6EUpBB5eXKTx7G1Esk,5213
85
+ foundry_mcp/core/providers/validation.py,sha256=ohhDPzvJOkt3y6vLx1_Dl8dTIu6kTtXT39zVm6HhzbA,25866
86
+ foundry_mcp/core/research/__init__.py,sha256=fEBsDm4Oz5qVgEN3etuEEx27jnDV3PkxndTlhOQwcCY,1478
87
+ foundry_mcp/core/research/memory.py,sha256=Z-hitvjiWBTJn1vdFyEECTlrCIaN2FBoirXP23pIdrU,17586
88
+ foundry_mcp/core/research/models.py,sha256=y9rycU0sR2rLXTNv11UscCVJrzeNy5WFtv90irsuYDk,43799
89
+ foundry_mcp/core/research/providers/__init__.py,sha256=KQ39_7XiWkQWVs95rdibZziv6rvHjKlOIcfWq6LihAs,1282
90
+ foundry_mcp/core/research/providers/base.py,sha256=Sso29K5AEr-6IYeIZNoa3LK7tCWoiUECkAQQCFzfjk4,7424
91
+ foundry_mcp/core/research/providers/google.py,sha256=UDthug1szRH1jz_DB0MRMaoj--F3joN0N0u79UVDhB0,17580
92
+ foundry_mcp/core/research/providers/perplexity.py,sha256=4PPZQvTSzjEHY4kxXkwC7h9Z8I5_YKNq6hRajYMvO_c,15093
93
+ foundry_mcp/core/research/providers/semantic_scholar.py,sha256=qGxw5z24yPs9NkKR7cBUKWqRM7gstRBxrPvrSEgW_9I,18993
94
+ foundry_mcp/core/research/providers/tavily.py,sha256=C-8S7jAsCmbuUfsSmZy7JFDZwHC6pPHc2IvHD2NdElE,13019
95
+ foundry_mcp/core/research/workflows/__init__.py,sha256=LHyTClhlGaENg5USVLZs_dWsMLS91YmeBxOC75YpVVQ,1048
96
+ foundry_mcp/core/research/workflows/base.py,sha256=yzGMXdYmGDP_RX9rn0LOdwEEc01ntoLvGNZYy0fvras,10254
97
+ foundry_mcp/core/research/workflows/chat.py,sha256=mrMRh2dyYWjxwc2yFNJL-hrqnb4na0yZuovSPz1c_7o,8313
98
+ foundry_mcp/core/research/workflows/consensus.py,sha256=lFLUB-gAS2KXojPdtBhASQgyKAssVQOQnJ1XmfYpno4,19325
99
+ foundry_mcp/core/research/workflows/deep_research.py,sha256=FhQt614fj_7VoW14N6nRUsCmkz1fQ2hPSIfWbBLUb9g,151261
100
+ foundry_mcp/core/research/workflows/ideate.py,sha256=SiV9RTIX4hsjmMDNSNRvD77OVYfbV_LGx-8SLmLyggc,22271
101
+ foundry_mcp/core/research/workflows/thinkdeep.py,sha256=5yWhDstcoxPSkxdyblPniXoB6GRqdFNOY-cjtAL-XaQ,14029
102
+ foundry_mcp/dashboard/__init__.py,sha256=3XUgs6JJbcrx37ABvZPosgNzd5oFqdDm7raNDqWkmFs,811
103
+ foundry_mcp/dashboard/app.py,sha256=Ktv0o6nBjtkXF08g2Oq15daR_hgV22vs1DlUZ55kJGI,3004
104
+ foundry_mcp/dashboard/launcher.py,sha256=budmYi7UkJc_bCITfKOCTL9kOO6MdkV87NreNaJTJ8o,8357
105
+ foundry_mcp/dashboard/components/__init__.py,sha256=etYzKPHwfZdYvm9u7s2CPR0VPnBmWHl8FyJ2YoE7mEI,352
106
+ foundry_mcp/dashboard/components/cards.py,sha256=QQTa4dlLyHwAUVe6zBwPoATmyIMXq24kTVRC0Uyvqno,2414
107
+ foundry_mcp/dashboard/components/charts.py,sha256=5iy7916wy9_jD2adePUB1KObUbcyRdMl-ZOwxxtsWsk,3978
108
+ foundry_mcp/dashboard/components/filters.py,sha256=uN7rFrBvZk6oacPcOiexET02BExNM5olWepjCXDrTf8,3061
109
+ foundry_mcp/dashboard/components/tables.py,sha256=WVq_BMbWBGyaIxuvy3qUrH6tBWsJABC5wfulojJNEK0,4873
110
+ foundry_mcp/dashboard/data/__init__.py,sha256=CFz07CIZhmD12kx77Mr2LG0J--b9DTvyu3uaHDJusvI,261
111
+ foundry_mcp/dashboard/data/stores.py,sha256=-2193Vdz1q4Ls2qmP1aQZ7cF1DWRxeCItq4LxJi6UnA,12391
112
+ foundry_mcp/dashboard/views/__init__.py,sha256=12fUzf3TkGMs0dJHfA6Y856qJEO7lOS_hs6msP3xYRs,208
113
+ foundry_mcp/dashboard/views/errors.py,sha256=suDaoQQsnyr2sYOWhN2nNNWIuIBZPkVIHGMlVvYsgr8,7305
114
+ foundry_mcp/dashboard/views/metrics.py,sha256=88Blo0iObfV_2mvhJV1RuqW7K278mn6vVoejonkTiuY,5725
115
+ foundry_mcp/dashboard/views/overview.py,sha256=BqtUVRC44PNXTd_Vu_PxVPZc3kThbytxANNGs3cmOhI,3369
116
+ foundry_mcp/dashboard/views/providers.py,sha256=sFCh-F8xIhLRQQzgjd_dwZz_S6AgolWSmP3iqczxxYE,2819
117
+ foundry_mcp/dashboard/views/sdd_workflow.py,sha256=lKvh8Ru6WBg5nE8vCcNZibemtRvhMoB3P5boK5Sd68M,8281
118
+ foundry_mcp/dashboard/views/tool_usage.py,sha256=-SD9VwhDJ1SRpHkYXDPINenSaLrQAiXRFEvRrp3-HbA,4760
119
+ foundry_mcp/prompts/__init__.py,sha256=AeAFELo1TTBdw4MVg4VoeDWsGGvzFTmdRtHHOBxwVzA,200
120
+ foundry_mcp/prompts/workflows.py,sha256=1bg43V6lEGeGuGX37G49UYGtDjZ6SZqsXguKb_zZQss,18369
121
+ foundry_mcp/resources/__init__.py,sha256=rnxmkQKsDcL-p-NyVlZtEh9Ie2BQn0nzQ_16NuCLBPY,201
122
+ foundry_mcp/resources/specs.py,sha256=HcXxZFJt8eDFTQpshwxC9PLdtYPbOttVGFbT8mLRO5A,19828
123
+ foundry_mcp/schemas/__init__.py,sha256=iKHwedYPusMeg-Ozdeq-gHDT5nhmqEPP-a9jXi8VyOM,1140
124
+ foundry_mcp/schemas/intake-schema.json,sha256=YisuGVRpMhg66lm-_vSs8SMVwjbsyWfXPSv0u4dI5UI,2691
125
+ foundry_mcp/schemas/sdd-spec-schema.json,sha256=VAmeihOuDHldgk-evwjZLPmoRt4o4mVZ1vCZkhWuh74,12480
126
+ foundry_mcp/tools/__init__.py,sha256=yNmGFFGNp-OMA758oAMxB4z7cvXb_6KPxm-ZyHbi-9I,199
127
+ foundry_mcp/tools/unified/__init__.py,sha256=cKkaNLCbe0XI1XlzndTrPZmjgqw1IJuaqv0Go1E67RU,3431
128
+ foundry_mcp/tools/unified/authoring.py,sha256=OU0EOY0ytxB9UCrcyHWkg4Eeyfp5-HQH8zIFVUMzA8Q,126735
129
+ foundry_mcp/tools/unified/context_helpers.py,sha256=JcaEdn1nVgaZ4GfTktRdZ7oD4pHkU9TH6O8WfWJ9lr0,3056
130
+ foundry_mcp/tools/unified/documentation_helpers.py,sha256=7kAt17bQiZ_WJ8BG2qDqZzdVGjBnIILlG5rzvJ4bfH4,10005
131
+ foundry_mcp/tools/unified/environment.py,sha256=1Uejax3XLh5pUhMGL186yM07frn4QSVm0z_8JzMFZng,46063
132
+ foundry_mcp/tools/unified/error.py,sha256=qzkI6GoFs4bfN79-x0fMpS1ogAn3lkdgzGGymUWEmE8,14452
133
+ foundry_mcp/tools/unified/health.py,sha256=8oo61HeTfuyxTYyuKQPLMHvvYh54ju33invxIGFi5s4,6833
134
+ foundry_mcp/tools/unified/journal.py,sha256=mbVivCiJymd6QFq2u1stVkqhLMM7odrpSyU6X9nRnak,23262
135
+ foundry_mcp/tools/unified/lifecycle.py,sha256=RKESL2wEaEHagDl61eRX0y0AivUoaPn8ibcdHunohmw,20436
136
+ foundry_mcp/tools/unified/metrics.py,sha256=Q1vIXLpQnTfHnpFxXKWwqQpNDc2_iuGIr48Bg7EM47c,22600
137
+ foundry_mcp/tools/unified/plan.py,sha256=XNfBy7SWrbgzOypZOVhBQ2pfgfGPMc2vm1kfQnsNdJE,30199
138
+ foundry_mcp/tools/unified/pr.py,sha256=L_Gk9oVP-8PgmbdULFr_BwXBPaLRib_55H8MyHuZ_34,9786
139
+ foundry_mcp/tools/unified/provider.py,sha256=rUIrtr4rh0YWEUoSbb-0GpM_Lx0dUuv2ElFvK8F0_O4,20558
140
+ foundry_mcp/tools/unified/research.py,sha256=ea3IibKCLF4T06zAW5LkXzg_r9mHQ0ofE5PYSF0M9Vg,43861
141
+ foundry_mcp/tools/unified/review.py,sha256=iA0AmIIE1qr15gmzzM1vAKixc_KEy2Y3_yUGfN9dI6U,37720
142
+ foundry_mcp/tools/unified/review_helpers.py,sha256=d4KPI-44Zwh9qvueywI1mcofLVPsscnVr2pcsvXYZBA,10253
143
+ foundry_mcp/tools/unified/router.py,sha256=sWqAaey9yhiAoxyrnL8lr2reMWyPgmyIPlwrNgZZmRI,3489
144
+ foundry_mcp/tools/unified/server.py,sha256=Krxwz011kXRmpjnUzP3ifwPkFN83tl5fhRczR3j9Isg,19218
145
+ foundry_mcp/tools/unified/spec.py,sha256=R2Fp8RjgqRUkCYMFafuIiuLpykvtqZ1IVZscFThnYaA,38792
146
+ foundry_mcp/tools/unified/task.py,sha256=2yR1Up_qbR14dqqxts2n2zjfCnkSry0sPGKoP0Cs8Zw,135247
147
+ foundry_mcp/tools/unified/test.py,sha256=7H2UObajxz0vHZ_1HUne4E7hlkVGnigT70SnDlkPlfk,13894
148
+ foundry_mcp/tools/unified/verification.py,sha256=Kq3BJ7-Rnp2R35dHoFSTdwHPjEf60nce8vWMIKOA32s,16945
149
+ foundry_mcp-0.8.10.dist-info/METADATA,sha256=EXKoBka7n7g9rdfTw0KCggui3XbhkM43RaLdEtcatjI,13901
150
+ foundry_mcp-0.8.10.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
151
+ foundry_mcp-0.8.10.dist-info/entry_points.txt,sha256=EBy4TVvlciNngt9ErVIyNjTImjczKa2FkoZRtIEw6aU,95
152
+ foundry_mcp-0.8.10.dist-info/licenses/LICENSE,sha256=8dabQwxo8HuixKsQaXcVaIHLLQWvPPLuKQFZLAIXL4w,1071
153
+ foundry_mcp-0.8.10.dist-info/RECORD,,
foundry_mcp/cli/flags.py DELETED
@@ -1,266 +0,0 @@
1
- """CLI feature flag bootstrap bridging CLI options and discovery manifest.
2
-
3
- Provides CLI-specific flag management that wraps the core feature flag
4
- infrastructure, enabling runtime flag overrides via CLI options and
5
- exposing flag status for tool discovery.
6
-
7
- See docs/mcp_best_practices/14-feature-flags.md for guidance.
8
- """
9
-
10
- from typing import Any, Callable, Dict, List, Optional, TypeVar
11
-
12
- import click
13
-
14
- from foundry_mcp.core.feature_flags import (
15
- FeatureFlag,
16
- FeatureFlagRegistry,
17
- FlagState,
18
- get_registry,
19
- )
20
-
21
- __all__ = [
22
- "CLIFlagRegistry",
23
- "get_cli_flags",
24
- "apply_cli_flag_overrides",
25
- "flags_for_discovery",
26
- "with_flag_options",
27
- ]
28
-
29
- T = TypeVar("T")
30
-
31
-
32
- class CLIFlagRegistry:
33
- """Registry of CLI-specific feature flags.
34
-
35
- Wraps the core FeatureFlagRegistry and provides CLI-specific
36
- functionality like mapping command-line options to flag overrides
37
- and generating discovery manifests.
38
-
39
- Example:
40
- >>> registry = CLIFlagRegistry()
41
- >>> registry.register_cli_flag(
42
- ... name="experimental_commands",
43
- ... description="Enable experimental CLI commands",
44
- ... default_enabled=False,
45
- ... state=FlagState.BETA,
46
- ... )
47
- >>> registry.is_enabled("experimental_commands")
48
- False
49
- """
50
-
51
- def __init__(self, core_registry: Optional[FeatureFlagRegistry] = None):
52
- """Initialize with optional core registry.
53
-
54
- Args:
55
- core_registry: Core feature flag registry. Uses global if None.
56
- """
57
- self._core = core_registry or get_registry()
58
- self._cli_flags: Dict[str, FeatureFlag] = {}
59
-
60
- def register_cli_flag(
61
- self,
62
- name: str,
63
- description: str,
64
- default_enabled: bool = False,
65
- state: FlagState = FlagState.BETA,
66
- **kwargs: Any,
67
- ) -> None:
68
- """Register a CLI-specific feature flag.
69
-
70
- Creates a flag in both the CLI registry and the core registry
71
- for unified evaluation.
72
-
73
- Args:
74
- name: Unique flag identifier (e.g., "experimental_commands").
75
- description: Human-readable description for discovery.
76
- default_enabled: Whether enabled by default.
77
- state: Flag lifecycle state.
78
- **kwargs: Additional FeatureFlag parameters.
79
- """
80
- flag = FeatureFlag(
81
- name=name,
82
- description=description,
83
- default_enabled=default_enabled,
84
- state=state,
85
- **kwargs,
86
- )
87
- self._cli_flags[name] = flag
88
- try:
89
- self._core.register(flag)
90
- except ValueError:
91
- # Flag already registered in core, update our local copy
92
- existing = self._core.get(name)
93
- if existing:
94
- self._cli_flags[name] = existing
95
-
96
- def is_enabled(self, flag_name: str, default: bool = False) -> bool:
97
- """Check if a CLI flag is enabled.
98
-
99
- Args:
100
- flag_name: Name of the flag to check.
101
- default: Value if flag doesn't exist.
102
-
103
- Returns:
104
- True if flag is enabled, False otherwise.
105
- """
106
- return self._core.is_enabled(flag_name, client_id="cli", default=default)
107
-
108
- def apply_overrides(self, overrides: Dict[str, bool]) -> None:
109
- """Apply multiple flag overrides.
110
-
111
- Used to translate CLI options into flag state. Overrides
112
- persist for the duration of the CLI command execution.
113
-
114
- Args:
115
- overrides: Mapping of flag names to enabled/disabled state.
116
- """
117
- for flag_name, enabled in overrides.items():
118
- self._core.set_override("cli", flag_name, enabled)
119
-
120
- def clear_overrides(self) -> None:
121
- """Clear all CLI-applied overrides."""
122
- self._core.clear_all_overrides("cli")
123
-
124
- def get_discovery_manifest(self) -> Dict[str, Dict[str, Any]]:
125
- """Generate discovery manifest for CLI flags.
126
-
127
- Returns flag information suitable for tool discovery responses,
128
- allowing AI coding assistants to understand available features.
129
-
130
- Returns:
131
- Dictionary with flag names as keys and info dicts as values.
132
- """
133
- manifest = {}
134
- for name, flag in self._cli_flags.items():
135
- manifest[name] = {
136
- "enabled": self.is_enabled(name),
137
- "state": flag.state.value,
138
- "description": flag.description,
139
- "default": flag.default_enabled,
140
- }
141
- if flag.state == FlagState.DEPRECATED and flag.expires_at:
142
- manifest[name]["expires"] = flag.expires_at.isoformat()
143
- return manifest
144
-
145
- def list_flags(self) -> List[str]:
146
- """List all registered CLI flag names."""
147
- return list(self._cli_flags.keys())
148
-
149
-
150
- # Global CLI flag registry
151
- _cli_registry: Optional[CLIFlagRegistry] = None
152
-
153
-
154
- def get_cli_flags() -> CLIFlagRegistry:
155
- """Get the global CLI flag registry."""
156
- global _cli_registry
157
- if _cli_registry is None:
158
- _cli_registry = CLIFlagRegistry()
159
- return _cli_registry
160
-
161
-
162
- def apply_cli_flag_overrides(
163
- enable: Optional[List[str]] = None,
164
- disable: Optional[List[str]] = None,
165
- ) -> None:
166
- """Apply flag overrides from CLI options.
167
-
168
- Translates --enable-feature and --disable-feature CLI options
169
- into feature flag overrides.
170
-
171
- Args:
172
- enable: List of flag names to enable.
173
- disable: List of flag names to disable.
174
- """
175
- registry = get_cli_flags()
176
- overrides: Dict[str, bool] = {}
177
-
178
- if enable:
179
- for flag_name in enable:
180
- overrides[flag_name] = True
181
-
182
- if disable:
183
- for flag_name in disable:
184
- overrides[flag_name] = False
185
-
186
- if overrides:
187
- registry.apply_overrides(overrides)
188
-
189
-
190
- def flags_for_discovery() -> Dict[str, Any]:
191
- """Get flag status for inclusion in discovery responses.
192
-
193
- Returns:
194
- Dictionary suitable for JSON serialization in discovery manifest.
195
- """
196
- return get_cli_flags().get_discovery_manifest()
197
-
198
-
199
- def with_flag_options(
200
- func: Optional[Callable[..., T]] = None,
201
- ) -> Callable[..., T]:
202
- """Click decorator that adds --enable-feature/--disable-feature options.
203
-
204
- Adds common flag override options to a Click command and applies
205
- them before command execution.
206
-
207
- Example:
208
- >>> @cli.command()
209
- ... @with_flag_options
210
- ... def my_command():
211
- ... # flags are already applied
212
- ... if get_cli_flags().is_enabled("experimental"):
213
- ... do_experimental_thing()
214
-
215
- Args:
216
- func: The Click command function to wrap.
217
-
218
- Returns:
219
- Decorated function with flag options.
220
- """
221
-
222
- def decorator(f: Callable[..., T]) -> Callable[..., T]:
223
- # Add the Click options
224
- f = click.option(
225
- "--enable-feature",
226
- "enable_features",
227
- multiple=True,
228
- help="Enable feature flag(s) for this command.",
229
- )(f)
230
- f = click.option(
231
- "--disable-feature",
232
- "disable_features",
233
- multiple=True,
234
- help="Disable feature flag(s) for this command.",
235
- )(f)
236
-
237
- # Wrap to apply flags before execution
238
- original = f
239
-
240
- @click.pass_context
241
- def wrapper(ctx: click.Context, *args: Any, **kwargs: Any) -> T:
242
- enable = kwargs.pop("enable_features", ())
243
- disable = kwargs.pop("disable_features", ())
244
-
245
- apply_cli_flag_overrides(
246
- enable=list(enable) if enable else None,
247
- disable=list(disable) if disable else None,
248
- )
249
-
250
- try:
251
- # Call original with remaining kwargs
252
- return ctx.invoke(original, *args, **kwargs)
253
- finally:
254
- # Clean up overrides after command
255
- get_cli_flags().clear_overrides()
256
-
257
- # Preserve function metadata
258
- wrapper.__name__ = f.__name__
259
- wrapper.__doc__ = f.__doc__
260
-
261
- return wrapper # type: ignore[return-value]
262
-
263
- if func is not None:
264
- return decorator(func)
265
-
266
- return decorator # type: ignore[return-value]