foundry-mcp 0.8.22__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.

Potentially problematic release.


This version of foundry-mcp might be problematic. Click here for more details.

Files changed (153) hide show
  1. foundry_mcp/__init__.py +13 -0
  2. foundry_mcp/cli/__init__.py +67 -0
  3. foundry_mcp/cli/__main__.py +9 -0
  4. foundry_mcp/cli/agent.py +96 -0
  5. foundry_mcp/cli/commands/__init__.py +37 -0
  6. foundry_mcp/cli/commands/cache.py +137 -0
  7. foundry_mcp/cli/commands/dashboard.py +148 -0
  8. foundry_mcp/cli/commands/dev.py +446 -0
  9. foundry_mcp/cli/commands/journal.py +377 -0
  10. foundry_mcp/cli/commands/lifecycle.py +274 -0
  11. foundry_mcp/cli/commands/modify.py +824 -0
  12. foundry_mcp/cli/commands/plan.py +640 -0
  13. foundry_mcp/cli/commands/pr.py +393 -0
  14. foundry_mcp/cli/commands/review.py +667 -0
  15. foundry_mcp/cli/commands/session.py +472 -0
  16. foundry_mcp/cli/commands/specs.py +686 -0
  17. foundry_mcp/cli/commands/tasks.py +807 -0
  18. foundry_mcp/cli/commands/testing.py +676 -0
  19. foundry_mcp/cli/commands/validate.py +982 -0
  20. foundry_mcp/cli/config.py +98 -0
  21. foundry_mcp/cli/context.py +298 -0
  22. foundry_mcp/cli/logging.py +212 -0
  23. foundry_mcp/cli/main.py +44 -0
  24. foundry_mcp/cli/output.py +122 -0
  25. foundry_mcp/cli/registry.py +110 -0
  26. foundry_mcp/cli/resilience.py +178 -0
  27. foundry_mcp/cli/transcript.py +217 -0
  28. foundry_mcp/config.py +1454 -0
  29. foundry_mcp/core/__init__.py +144 -0
  30. foundry_mcp/core/ai_consultation.py +1773 -0
  31. foundry_mcp/core/batch_operations.py +1202 -0
  32. foundry_mcp/core/cache.py +195 -0
  33. foundry_mcp/core/capabilities.py +446 -0
  34. foundry_mcp/core/concurrency.py +898 -0
  35. foundry_mcp/core/context.py +540 -0
  36. foundry_mcp/core/discovery.py +1603 -0
  37. foundry_mcp/core/error_collection.py +728 -0
  38. foundry_mcp/core/error_store.py +592 -0
  39. foundry_mcp/core/health.py +749 -0
  40. foundry_mcp/core/intake.py +933 -0
  41. foundry_mcp/core/journal.py +700 -0
  42. foundry_mcp/core/lifecycle.py +412 -0
  43. foundry_mcp/core/llm_config.py +1376 -0
  44. foundry_mcp/core/llm_patterns.py +510 -0
  45. foundry_mcp/core/llm_provider.py +1569 -0
  46. foundry_mcp/core/logging_config.py +374 -0
  47. foundry_mcp/core/metrics_persistence.py +584 -0
  48. foundry_mcp/core/metrics_registry.py +327 -0
  49. foundry_mcp/core/metrics_store.py +641 -0
  50. foundry_mcp/core/modifications.py +224 -0
  51. foundry_mcp/core/naming.py +146 -0
  52. foundry_mcp/core/observability.py +1216 -0
  53. foundry_mcp/core/otel.py +452 -0
  54. foundry_mcp/core/otel_stubs.py +264 -0
  55. foundry_mcp/core/pagination.py +255 -0
  56. foundry_mcp/core/progress.py +387 -0
  57. foundry_mcp/core/prometheus.py +564 -0
  58. foundry_mcp/core/prompts/__init__.py +464 -0
  59. foundry_mcp/core/prompts/fidelity_review.py +691 -0
  60. foundry_mcp/core/prompts/markdown_plan_review.py +515 -0
  61. foundry_mcp/core/prompts/plan_review.py +627 -0
  62. foundry_mcp/core/providers/__init__.py +237 -0
  63. foundry_mcp/core/providers/base.py +515 -0
  64. foundry_mcp/core/providers/claude.py +472 -0
  65. foundry_mcp/core/providers/codex.py +637 -0
  66. foundry_mcp/core/providers/cursor_agent.py +630 -0
  67. foundry_mcp/core/providers/detectors.py +515 -0
  68. foundry_mcp/core/providers/gemini.py +426 -0
  69. foundry_mcp/core/providers/opencode.py +718 -0
  70. foundry_mcp/core/providers/opencode_wrapper.js +308 -0
  71. foundry_mcp/core/providers/package-lock.json +24 -0
  72. foundry_mcp/core/providers/package.json +25 -0
  73. foundry_mcp/core/providers/registry.py +607 -0
  74. foundry_mcp/core/providers/test_provider.py +171 -0
  75. foundry_mcp/core/providers/validation.py +857 -0
  76. foundry_mcp/core/rate_limit.py +427 -0
  77. foundry_mcp/core/research/__init__.py +68 -0
  78. foundry_mcp/core/research/memory.py +528 -0
  79. foundry_mcp/core/research/models.py +1234 -0
  80. foundry_mcp/core/research/providers/__init__.py +40 -0
  81. foundry_mcp/core/research/providers/base.py +242 -0
  82. foundry_mcp/core/research/providers/google.py +507 -0
  83. foundry_mcp/core/research/providers/perplexity.py +442 -0
  84. foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
  85. foundry_mcp/core/research/providers/tavily.py +383 -0
  86. foundry_mcp/core/research/workflows/__init__.py +25 -0
  87. foundry_mcp/core/research/workflows/base.py +298 -0
  88. foundry_mcp/core/research/workflows/chat.py +271 -0
  89. foundry_mcp/core/research/workflows/consensus.py +539 -0
  90. foundry_mcp/core/research/workflows/deep_research.py +4142 -0
  91. foundry_mcp/core/research/workflows/ideate.py +682 -0
  92. foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
  93. foundry_mcp/core/resilience.py +600 -0
  94. foundry_mcp/core/responses.py +1624 -0
  95. foundry_mcp/core/review.py +366 -0
  96. foundry_mcp/core/security.py +438 -0
  97. foundry_mcp/core/spec.py +4119 -0
  98. foundry_mcp/core/task.py +2463 -0
  99. foundry_mcp/core/testing.py +839 -0
  100. foundry_mcp/core/validation.py +2357 -0
  101. foundry_mcp/dashboard/__init__.py +32 -0
  102. foundry_mcp/dashboard/app.py +119 -0
  103. foundry_mcp/dashboard/components/__init__.py +17 -0
  104. foundry_mcp/dashboard/components/cards.py +88 -0
  105. foundry_mcp/dashboard/components/charts.py +177 -0
  106. foundry_mcp/dashboard/components/filters.py +136 -0
  107. foundry_mcp/dashboard/components/tables.py +195 -0
  108. foundry_mcp/dashboard/data/__init__.py +11 -0
  109. foundry_mcp/dashboard/data/stores.py +433 -0
  110. foundry_mcp/dashboard/launcher.py +300 -0
  111. foundry_mcp/dashboard/views/__init__.py +12 -0
  112. foundry_mcp/dashboard/views/errors.py +217 -0
  113. foundry_mcp/dashboard/views/metrics.py +164 -0
  114. foundry_mcp/dashboard/views/overview.py +96 -0
  115. foundry_mcp/dashboard/views/providers.py +83 -0
  116. foundry_mcp/dashboard/views/sdd_workflow.py +255 -0
  117. foundry_mcp/dashboard/views/tool_usage.py +139 -0
  118. foundry_mcp/prompts/__init__.py +9 -0
  119. foundry_mcp/prompts/workflows.py +525 -0
  120. foundry_mcp/resources/__init__.py +9 -0
  121. foundry_mcp/resources/specs.py +591 -0
  122. foundry_mcp/schemas/__init__.py +38 -0
  123. foundry_mcp/schemas/intake-schema.json +89 -0
  124. foundry_mcp/schemas/sdd-spec-schema.json +414 -0
  125. foundry_mcp/server.py +150 -0
  126. foundry_mcp/tools/__init__.py +10 -0
  127. foundry_mcp/tools/unified/__init__.py +92 -0
  128. foundry_mcp/tools/unified/authoring.py +3620 -0
  129. foundry_mcp/tools/unified/context_helpers.py +98 -0
  130. foundry_mcp/tools/unified/documentation_helpers.py +268 -0
  131. foundry_mcp/tools/unified/environment.py +1341 -0
  132. foundry_mcp/tools/unified/error.py +479 -0
  133. foundry_mcp/tools/unified/health.py +225 -0
  134. foundry_mcp/tools/unified/journal.py +841 -0
  135. foundry_mcp/tools/unified/lifecycle.py +640 -0
  136. foundry_mcp/tools/unified/metrics.py +777 -0
  137. foundry_mcp/tools/unified/plan.py +876 -0
  138. foundry_mcp/tools/unified/pr.py +294 -0
  139. foundry_mcp/tools/unified/provider.py +589 -0
  140. foundry_mcp/tools/unified/research.py +1283 -0
  141. foundry_mcp/tools/unified/review.py +1042 -0
  142. foundry_mcp/tools/unified/review_helpers.py +314 -0
  143. foundry_mcp/tools/unified/router.py +102 -0
  144. foundry_mcp/tools/unified/server.py +565 -0
  145. foundry_mcp/tools/unified/spec.py +1283 -0
  146. foundry_mcp/tools/unified/task.py +3846 -0
  147. foundry_mcp/tools/unified/test.py +431 -0
  148. foundry_mcp/tools/unified/verification.py +520 -0
  149. foundry_mcp-0.8.22.dist-info/METADATA +344 -0
  150. foundry_mcp-0.8.22.dist-info/RECORD +153 -0
  151. foundry_mcp-0.8.22.dist-info/WHEEL +4 -0
  152. foundry_mcp-0.8.22.dist-info/entry_points.txt +3 -0
  153. foundry_mcp-0.8.22.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,446 @@
1
+ """Development utility commands for SDD CLI.
2
+
3
+ Provides commands for skills development including:
4
+ - Documentation generation helpers
5
+ - Installation helpers
6
+ - Server start helpers
7
+ - Development tooling
8
+ """
9
+
10
+ import subprocess
11
+ import time
12
+ from pathlib import Path
13
+ from typing import Optional
14
+
15
+ import click
16
+
17
+ from foundry_mcp.cli.logging import cli_command, get_cli_logger
18
+ from foundry_mcp.cli.output import emit_error, emit_success
19
+ from foundry_mcp.cli.registry import get_context
20
+ from foundry_mcp.cli.resilience import (
21
+ FAST_TIMEOUT,
22
+ MEDIUM_TIMEOUT,
23
+ handle_keyboard_interrupt,
24
+ with_sync_timeout,
25
+ )
26
+
27
+ logger = get_cli_logger()
28
+
29
+
30
+ @click.group("dev")
31
+ def dev_group() -> None:
32
+ """Development utility commands."""
33
+ pass
34
+
35
+
36
+ @dev_group.command("gendocs")
37
+ @click.option(
38
+ "--output-dir",
39
+ default="docs",
40
+ help="Output directory for generated documentation.",
41
+ )
42
+ @click.option(
43
+ "--format",
44
+ "doc_format",
45
+ type=click.Choice(["markdown", "html", "json"]),
46
+ default="markdown",
47
+ help="Output format for documentation.",
48
+ )
49
+ @click.option(
50
+ "--include-private",
51
+ is_flag=True,
52
+ help="Include private/internal APIs in documentation.",
53
+ )
54
+ @click.pass_context
55
+ @cli_command("gendocs")
56
+ @handle_keyboard_interrupt()
57
+ @with_sync_timeout(MEDIUM_TIMEOUT, "Documentation generation timed out")
58
+ def dev_gendocs_cmd(
59
+ ctx: click.Context,
60
+ output_dir: str,
61
+ doc_format: str,
62
+ include_private: bool,
63
+ ) -> None:
64
+ """Generate documentation from source code.
65
+
66
+ Scans the codebase and generates API documentation.
67
+ """
68
+ start_time = time.perf_counter()
69
+ cli_ctx = get_context(ctx)
70
+
71
+ # Determine project root
72
+ project_root = cli_ctx.specs_dir.parent if cli_ctx.specs_dir else Path.cwd()
73
+ output_path = project_root / output_dir
74
+
75
+ # Create output directory if needed
76
+ output_path.mkdir(parents=True, exist_ok=True)
77
+
78
+ # Check for common doc generators
79
+ generators = []
80
+
81
+ # Check pdoc
82
+ try:
83
+ result = subprocess.run(
84
+ ["pdoc", "--version"],
85
+ capture_output=True,
86
+ timeout=5,
87
+ )
88
+ if result.returncode == 0:
89
+ generators.append("pdoc")
90
+ except (FileNotFoundError, subprocess.TimeoutExpired):
91
+ pass
92
+
93
+ # Check sphinx
94
+ try:
95
+ result = subprocess.run(
96
+ ["sphinx-build", "--version"],
97
+ capture_output=True,
98
+ timeout=5,
99
+ )
100
+ if result.returncode == 0:
101
+ generators.append("sphinx")
102
+ except (FileNotFoundError, subprocess.TimeoutExpired):
103
+ pass
104
+
105
+ duration_ms = (time.perf_counter() - start_time) * 1000
106
+
107
+ if not generators:
108
+ emit_success(
109
+ {
110
+ "status": "no_generator",
111
+ "output_dir": str(output_path),
112
+ "format": doc_format,
113
+ "available_generators": [],
114
+ "recommendations": [
115
+ "Install pdoc: pip install pdoc",
116
+ "Or install sphinx: pip install sphinx",
117
+ ],
118
+ "telemetry": {"duration_ms": round(duration_ms, 2)},
119
+ }
120
+ )
121
+ return
122
+
123
+ emit_success(
124
+ {
125
+ "status": "ready",
126
+ "output_dir": str(output_path),
127
+ "format": doc_format,
128
+ "available_generators": generators,
129
+ "include_private": include_private,
130
+ "telemetry": {"duration_ms": round(duration_ms, 2)},
131
+ }
132
+ )
133
+
134
+
135
+ @dev_group.command("install")
136
+ @click.option(
137
+ "--dev",
138
+ is_flag=True,
139
+ help="Install with development dependencies.",
140
+ )
141
+ @click.option(
142
+ "--editable/--no-editable",
143
+ default=True,
144
+ help="Install in editable mode (default: true).",
145
+ )
146
+ @click.option(
147
+ "--extras",
148
+ help="Comma-separated list of extras to install.",
149
+ )
150
+ @click.pass_context
151
+ @cli_command("install")
152
+ @handle_keyboard_interrupt()
153
+ @with_sync_timeout(300, "Installation timed out")
154
+ def dev_install_cmd(
155
+ ctx: click.Context,
156
+ dev: bool,
157
+ editable: bool,
158
+ extras: Optional[str],
159
+ ) -> None:
160
+ """Install the package for development.
161
+
162
+ Installs the current package with optional dev dependencies.
163
+ """
164
+ start_time = time.perf_counter()
165
+ cli_ctx = get_context(ctx)
166
+
167
+ # Determine project root
168
+ project_root = cli_ctx.specs_dir.parent if cli_ctx.specs_dir else Path.cwd()
169
+
170
+ # Check for pyproject.toml or setup.py
171
+ has_pyproject = (project_root / "pyproject.toml").exists()
172
+ has_setup = (project_root / "setup.py").exists()
173
+
174
+ if not has_pyproject and not has_setup:
175
+ emit_error(
176
+ "No Python project found",
177
+ code="NO_PROJECT",
178
+ error_type="validation",
179
+ remediation="Ensure pyproject.toml or setup.py exists in the project root",
180
+ details={
181
+ "hint": "Ensure pyproject.toml or setup.py exists",
182
+ "project_root": str(project_root),
183
+ },
184
+ )
185
+ return
186
+
187
+ # Build pip install command
188
+ cmd = ["pip", "install"]
189
+
190
+ if editable:
191
+ cmd.append("-e")
192
+
193
+ # Build package specifier
194
+ package_spec = "."
195
+ if extras:
196
+ package_spec = f".[{extras}]"
197
+ if dev:
198
+ if extras:
199
+ package_spec = f".[dev,{extras}]"
200
+ else:
201
+ package_spec = ".[dev]"
202
+
203
+ cmd.append(package_spec)
204
+
205
+ try:
206
+ result = subprocess.run(
207
+ cmd,
208
+ capture_output=True,
209
+ text=True,
210
+ timeout=300,
211
+ cwd=str(project_root),
212
+ )
213
+
214
+ duration_ms = (time.perf_counter() - start_time) * 1000
215
+
216
+ if result.returncode != 0:
217
+ emit_error(
218
+ "Installation failed",
219
+ code="INSTALL_FAILED",
220
+ error_type="internal",
221
+ remediation="Check the error output and ensure all dependencies are available",
222
+ details={
223
+ "exit_code": result.returncode,
224
+ "stderr": result.stderr[:500] if result.stderr else None,
225
+ },
226
+ )
227
+ return
228
+
229
+ emit_success(
230
+ {
231
+ "status": "installed",
232
+ "editable": editable,
233
+ "dev": dev,
234
+ "extras": extras,
235
+ "project_root": str(project_root),
236
+ "telemetry": {"duration_ms": round(duration_ms, 2)},
237
+ }
238
+ )
239
+
240
+ except subprocess.TimeoutExpired:
241
+ emit_error(
242
+ "Installation timed out",
243
+ code="TIMEOUT",
244
+ error_type="internal",
245
+ remediation="Try again with a faster network or fewer dependencies",
246
+ details={"timeout_seconds": 300},
247
+ )
248
+ except FileNotFoundError:
249
+ emit_error(
250
+ "pip not found",
251
+ code="PIP_NOT_FOUND",
252
+ error_type="internal",
253
+ remediation="Ensure pip is installed and in PATH",
254
+ details={"hint": "Ensure pip is installed and in PATH"},
255
+ )
256
+
257
+
258
+ @dev_group.command("start")
259
+ @click.option(
260
+ "--port",
261
+ type=int,
262
+ default=8000,
263
+ help="Port to run the development server on.",
264
+ )
265
+ @click.option(
266
+ "--host",
267
+ default="127.0.0.1",
268
+ help="Host to bind the server to.",
269
+ )
270
+ @click.option(
271
+ "--reload/--no-reload",
272
+ default=True,
273
+ help="Enable auto-reload on file changes.",
274
+ )
275
+ @click.pass_context
276
+ @cli_command("start")
277
+ @handle_keyboard_interrupt()
278
+ @with_sync_timeout(FAST_TIMEOUT, "Server check timed out")
279
+ def dev_start_cmd(
280
+ ctx: click.Context,
281
+ port: int,
282
+ host: str,
283
+ reload: bool,
284
+ ) -> None:
285
+ """Start a development server.
286
+
287
+ Checks for common development server configurations and
288
+ provides instructions for starting.
289
+ """
290
+ start_time = time.perf_counter()
291
+ cli_ctx = get_context(ctx)
292
+
293
+ # Determine project root
294
+ project_root = cli_ctx.specs_dir.parent if cli_ctx.specs_dir else Path.cwd()
295
+
296
+ # Check for various server configurations
297
+ server_configs = []
298
+
299
+ # Check for uvicorn (FastAPI/Starlette)
300
+ try:
301
+ result = subprocess.run(
302
+ ["uvicorn", "--version"],
303
+ capture_output=True,
304
+ timeout=5,
305
+ )
306
+ if result.returncode == 0:
307
+ server_configs.append(
308
+ {
309
+ "name": "uvicorn",
310
+ "command": f"uvicorn main:app --host {host} --port {port}"
311
+ + (" --reload" if reload else ""),
312
+ }
313
+ )
314
+ except (FileNotFoundError, subprocess.TimeoutExpired):
315
+ pass
316
+
317
+ # Check for flask
318
+ try:
319
+ result = subprocess.run(
320
+ ["flask", "--version"],
321
+ capture_output=True,
322
+ timeout=5,
323
+ )
324
+ if result.returncode == 0:
325
+ server_configs.append(
326
+ {
327
+ "name": "flask",
328
+ "command": f"flask run --host {host} --port {port}"
329
+ + (" --reload" if reload else ""),
330
+ }
331
+ )
332
+ except (FileNotFoundError, subprocess.TimeoutExpired):
333
+ pass
334
+
335
+ # Check for MCP server config
336
+ mcp_config = project_root / "mcp.json"
337
+ if mcp_config.exists():
338
+ server_configs.append(
339
+ {
340
+ "name": "mcp",
341
+ "command": f"python -m foundry_mcp.server --port {port}",
342
+ }
343
+ )
344
+
345
+ duration_ms = (time.perf_counter() - start_time) * 1000
346
+
347
+ emit_success(
348
+ {
349
+ "host": host,
350
+ "port": port,
351
+ "reload": reload,
352
+ "available_servers": server_configs,
353
+ "project_root": str(project_root),
354
+ "telemetry": {"duration_ms": round(duration_ms, 2)},
355
+ }
356
+ )
357
+
358
+
359
+ @dev_group.command("check")
360
+ @click.pass_context
361
+ @cli_command("check")
362
+ @handle_keyboard_interrupt()
363
+ @with_sync_timeout(FAST_TIMEOUT, "Environment check timed out")
364
+ def dev_check_cmd(ctx: click.Context) -> None:
365
+ """Check development environment status.
366
+
367
+ Verifies that all development tools are available.
368
+ """
369
+ start_time = time.perf_counter()
370
+ get_context(ctx)
371
+
372
+ tools = {}
373
+
374
+ # Check Python
375
+ try:
376
+ result = subprocess.run(
377
+ ["python", "--version"],
378
+ capture_output=True,
379
+ text=True,
380
+ timeout=5,
381
+ )
382
+ tools["python"] = {
383
+ "available": result.returncode == 0,
384
+ "version": result.stdout.strip() if result.returncode == 0 else None,
385
+ }
386
+ except (FileNotFoundError, subprocess.TimeoutExpired):
387
+ tools["python"] = {"available": False}
388
+
389
+ # Check pip
390
+ try:
391
+ result = subprocess.run(
392
+ ["pip", "--version"],
393
+ capture_output=True,
394
+ text=True,
395
+ timeout=5,
396
+ )
397
+ tools["pip"] = {
398
+ "available": result.returncode == 0,
399
+ "version": result.stdout.split()[1] if result.returncode == 0 else None,
400
+ }
401
+ except (FileNotFoundError, subprocess.TimeoutExpired):
402
+ tools["pip"] = {"available": False}
403
+
404
+ # Check git
405
+ try:
406
+ result = subprocess.run(
407
+ ["git", "--version"],
408
+ capture_output=True,
409
+ text=True,
410
+ timeout=5,
411
+ )
412
+ tools["git"] = {
413
+ "available": result.returncode == 0,
414
+ "version": result.stdout.strip() if result.returncode == 0 else None,
415
+ }
416
+ except (FileNotFoundError, subprocess.TimeoutExpired):
417
+ tools["git"] = {"available": False}
418
+
419
+ # Check node (optional)
420
+ try:
421
+ result = subprocess.run(
422
+ ["node", "--version"],
423
+ capture_output=True,
424
+ text=True,
425
+ timeout=5,
426
+ )
427
+ tools["node"] = {
428
+ "available": result.returncode == 0,
429
+ "version": result.stdout.strip() if result.returncode == 0 else None,
430
+ }
431
+ except (FileNotFoundError, subprocess.TimeoutExpired):
432
+ tools["node"] = {"available": False}
433
+
434
+ duration_ms = (time.perf_counter() - start_time) * 1000
435
+
436
+ all_required = all(
437
+ tools.get(t, {}).get("available", False) for t in ["python", "pip", "git"]
438
+ )
439
+
440
+ emit_success(
441
+ {
442
+ "tools": tools,
443
+ "all_required_available": all_required,
444
+ "telemetry": {"duration_ms": round(duration_ms, 2)},
445
+ }
446
+ )