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.
- foundry_mcp/__init__.py +7 -1
- foundry_mcp/cli/__init__.py +0 -13
- foundry_mcp/cli/commands/plan.py +10 -3
- foundry_mcp/cli/commands/review.py +19 -4
- foundry_mcp/cli/commands/session.py +1 -8
- foundry_mcp/cli/commands/specs.py +38 -208
- foundry_mcp/cli/context.py +39 -0
- foundry_mcp/cli/output.py +3 -3
- foundry_mcp/config.py +615 -11
- foundry_mcp/core/ai_consultation.py +146 -9
- foundry_mcp/core/batch_operations.py +1196 -0
- foundry_mcp/core/discovery.py +7 -7
- foundry_mcp/core/error_store.py +2 -2
- foundry_mcp/core/intake.py +933 -0
- foundry_mcp/core/llm_config.py +28 -2
- foundry_mcp/core/metrics_store.py +2 -2
- foundry_mcp/core/naming.py +25 -2
- foundry_mcp/core/progress.py +70 -0
- foundry_mcp/core/prometheus.py +0 -13
- foundry_mcp/core/prompts/fidelity_review.py +149 -4
- foundry_mcp/core/prompts/markdown_plan_review.py +5 -1
- foundry_mcp/core/prompts/plan_review.py +5 -1
- foundry_mcp/core/providers/__init__.py +12 -0
- foundry_mcp/core/providers/base.py +39 -0
- foundry_mcp/core/providers/claude.py +51 -48
- foundry_mcp/core/providers/codex.py +70 -60
- foundry_mcp/core/providers/cursor_agent.py +25 -47
- foundry_mcp/core/providers/detectors.py +34 -7
- foundry_mcp/core/providers/gemini.py +69 -58
- foundry_mcp/core/providers/opencode.py +101 -47
- foundry_mcp/core/providers/package-lock.json +4 -4
- foundry_mcp/core/providers/package.json +1 -1
- foundry_mcp/core/providers/validation.py +128 -0
- foundry_mcp/core/research/__init__.py +68 -0
- foundry_mcp/core/research/memory.py +528 -0
- foundry_mcp/core/research/models.py +1220 -0
- foundry_mcp/core/research/providers/__init__.py +40 -0
- foundry_mcp/core/research/providers/base.py +242 -0
- foundry_mcp/core/research/providers/google.py +507 -0
- foundry_mcp/core/research/providers/perplexity.py +442 -0
- foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
- foundry_mcp/core/research/providers/tavily.py +383 -0
- foundry_mcp/core/research/workflows/__init__.py +25 -0
- foundry_mcp/core/research/workflows/base.py +298 -0
- foundry_mcp/core/research/workflows/chat.py +271 -0
- foundry_mcp/core/research/workflows/consensus.py +539 -0
- foundry_mcp/core/research/workflows/deep_research.py +4020 -0
- foundry_mcp/core/research/workflows/ideate.py +682 -0
- foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
- foundry_mcp/core/responses.py +690 -0
- foundry_mcp/core/spec.py +2439 -236
- foundry_mcp/core/task.py +1205 -31
- foundry_mcp/core/testing.py +512 -123
- foundry_mcp/core/validation.py +319 -43
- foundry_mcp/dashboard/components/charts.py +0 -57
- foundry_mcp/dashboard/launcher.py +11 -0
- foundry_mcp/dashboard/views/metrics.py +25 -35
- foundry_mcp/dashboard/views/overview.py +1 -65
- foundry_mcp/resources/specs.py +25 -25
- foundry_mcp/schemas/intake-schema.json +89 -0
- foundry_mcp/schemas/sdd-spec-schema.json +33 -5
- foundry_mcp/server.py +0 -14
- foundry_mcp/tools/unified/__init__.py +39 -18
- foundry_mcp/tools/unified/authoring.py +2371 -248
- foundry_mcp/tools/unified/documentation_helpers.py +69 -6
- foundry_mcp/tools/unified/environment.py +434 -32
- foundry_mcp/tools/unified/error.py +18 -1
- foundry_mcp/tools/unified/lifecycle.py +8 -0
- foundry_mcp/tools/unified/plan.py +133 -2
- foundry_mcp/tools/unified/provider.py +0 -40
- foundry_mcp/tools/unified/research.py +1283 -0
- foundry_mcp/tools/unified/review.py +374 -17
- foundry_mcp/tools/unified/review_helpers.py +16 -1
- foundry_mcp/tools/unified/server.py +9 -24
- foundry_mcp/tools/unified/spec.py +367 -0
- foundry_mcp/tools/unified/task.py +1664 -30
- foundry_mcp/tools/unified/test.py +69 -8
- {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/METADATA +8 -1
- foundry_mcp-0.8.10.dist-info/RECORD +153 -0
- foundry_mcp/cli/flags.py +0 -266
- foundry_mcp/core/feature_flags.py +0 -592
- foundry_mcp-0.3.3.dist-info/RECORD +0 -135
- {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/WHEEL +0 -0
- {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/entry_points.txt +0 -0
- {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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
295
|
-
"discover": "Discover
|
|
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
|
+
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]
|