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,393 @@
1
+ """PR workflow commands for SDD CLI.
2
+
3
+ Provides commands for creating GitHub PRs with SDD spec context
4
+ and AI-enhanced PR descriptions.
5
+ """
6
+
7
+ import json
8
+ import subprocess
9
+ import time
10
+ from typing import Any, Dict, Optional, cast
11
+
12
+ import click
13
+
14
+ from foundry_mcp.cli.logging import cli_command, get_cli_logger
15
+ from foundry_mcp.cli.output import emit_error, emit_success
16
+ from foundry_mcp.cli.registry import get_context
17
+ from foundry_mcp.cli.resilience import (
18
+ FAST_TIMEOUT,
19
+ MEDIUM_TIMEOUT,
20
+ with_sync_timeout,
21
+ handle_keyboard_interrupt,
22
+ )
23
+
24
+ logger = get_cli_logger()
25
+
26
+ # Default timeout for PR operations
27
+ PR_TIMEOUT = 120
28
+
29
+
30
+ @click.group("pr")
31
+ def pr_group() -> None:
32
+ """Pull request workflow commands."""
33
+ pass
34
+
35
+
36
+ @pr_group.command("create")
37
+ @click.argument("spec_id")
38
+ @click.option(
39
+ "--title",
40
+ help="PR title (default: auto-generated from spec).",
41
+ )
42
+ @click.option(
43
+ "--base",
44
+ "base_branch",
45
+ default="main",
46
+ help="Base branch for PR.",
47
+ )
48
+ @click.option(
49
+ "--include-journals/--no-journals",
50
+ default=True,
51
+ help="Include journal entries in PR description.",
52
+ )
53
+ @click.option(
54
+ "--include-diffs/--no-diffs",
55
+ default=True,
56
+ help="Include git diffs in LLM context.",
57
+ )
58
+ @click.option(
59
+ "--model",
60
+ help="LLM model for description generation.",
61
+ )
62
+ @click.option(
63
+ "--dry-run",
64
+ is_flag=True,
65
+ help="Preview PR content without creating.",
66
+ )
67
+ @click.pass_context
68
+ @cli_command("create")
69
+ @handle_keyboard_interrupt()
70
+ @with_sync_timeout(PR_TIMEOUT, "PR creation timed out")
71
+ def pr_create_cmd(
72
+ ctx: click.Context,
73
+ spec_id: str,
74
+ title: Optional[str],
75
+ base_branch: str,
76
+ include_journals: bool,
77
+ include_diffs: bool,
78
+ model: Optional[str],
79
+ dry_run: bool,
80
+ ) -> None:
81
+ """Create a GitHub PR with AI-enhanced description from SDD spec.
82
+
83
+ SPEC_ID is the specification identifier.
84
+
85
+ Uses spec context (tasks, journals, progress) to generate
86
+ comprehensive PR descriptions. Requires GitHub CLI (gh).
87
+ """
88
+ start_time = time.perf_counter()
89
+ cli_ctx = get_context(ctx)
90
+ specs_dir = cli_ctx.specs_dir
91
+
92
+ if specs_dir is None:
93
+ emit_error(
94
+ "No specs directory found",
95
+ code="VALIDATION_ERROR",
96
+ error_type="validation",
97
+ remediation="Use --specs-dir option or set SDD_SPECS_DIR environment variable",
98
+ details={"hint": "Use --specs-dir or set SDD_SPECS_DIR"},
99
+ )
100
+ return
101
+
102
+ # Get LLM status
103
+ llm_status = _get_llm_status()
104
+
105
+ # Build command
106
+ cmd = ["sdd", "create-pr", spec_id, "--json"]
107
+
108
+ if title:
109
+ cmd.extend(["--title", title])
110
+ cmd.extend(["--base", base_branch])
111
+
112
+ if include_journals:
113
+ cmd.append("--include-journals")
114
+ if include_diffs:
115
+ cmd.append("--include-diffs")
116
+ if model:
117
+ cmd.extend(["--model", model])
118
+ if specs_dir:
119
+ cmd.extend(["--path", str(specs_dir.parent)])
120
+ if dry_run:
121
+ cmd.append("--dry-run")
122
+
123
+ try:
124
+ result = subprocess.run(
125
+ cmd,
126
+ capture_output=True,
127
+ text=True,
128
+ timeout=PR_TIMEOUT,
129
+ )
130
+
131
+ duration_ms = (time.perf_counter() - start_time) * 1000
132
+
133
+ if result.returncode != 0:
134
+ error_msg = result.stderr.strip() if result.stderr else "PR creation failed"
135
+ emit_error(
136
+ f"PR creation failed: {error_msg}",
137
+ code="PR_FAILED",
138
+ error_type="internal",
139
+ remediation="Ensure GitHub CLI is authenticated and repository is properly configured",
140
+ details={
141
+ "spec_id": spec_id,
142
+ "exit_code": result.returncode,
143
+ },
144
+ )
145
+ return
146
+
147
+ # Parse output
148
+ try:
149
+ pr_data = json.loads(result.stdout)
150
+ except json.JSONDecodeError:
151
+ pr_data = {"raw_output": result.stdout}
152
+
153
+ emit_success(
154
+ {
155
+ "spec_id": spec_id,
156
+ "dry_run": dry_run,
157
+ "llm_status": llm_status,
158
+ **pr_data,
159
+ "telemetry": {"duration_ms": round(duration_ms, 2)},
160
+ }
161
+ )
162
+
163
+ except subprocess.TimeoutExpired:
164
+ emit_error(
165
+ f"PR creation timed out after {PR_TIMEOUT}s",
166
+ code="TIMEOUT",
167
+ error_type="internal",
168
+ remediation="Check network connectivity and GitHub API status",
169
+ details={
170
+ "spec_id": spec_id,
171
+ "timeout_seconds": PR_TIMEOUT,
172
+ },
173
+ )
174
+ except FileNotFoundError:
175
+ emit_error(
176
+ "SDD CLI not found",
177
+ code="CLI_NOT_FOUND",
178
+ error_type="internal",
179
+ remediation="Ensure 'sdd' is installed and in PATH",
180
+ details={"hint": "Ensure 'sdd' is installed and in PATH"},
181
+ )
182
+
183
+
184
+ @pr_group.command("context")
185
+ @click.argument("spec_id")
186
+ @click.option(
187
+ "--include-tasks/--no-tasks",
188
+ default=True,
189
+ help="Include completed task summaries.",
190
+ )
191
+ @click.option(
192
+ "--include-journals/--no-journals",
193
+ default=True,
194
+ help="Include recent journal entries.",
195
+ )
196
+ @click.option(
197
+ "--include-progress/--no-progress",
198
+ default=True,
199
+ help="Include phase/task progress stats.",
200
+ )
201
+ @click.pass_context
202
+ @cli_command("context")
203
+ @handle_keyboard_interrupt()
204
+ @with_sync_timeout(MEDIUM_TIMEOUT, "PR context lookup timed out")
205
+ def pr_context_cmd(
206
+ ctx: click.Context,
207
+ spec_id: str,
208
+ include_tasks: bool,
209
+ include_journals: bool,
210
+ include_progress: bool,
211
+ ) -> None:
212
+ """Get specification context for PR description generation.
213
+
214
+ SPEC_ID is the specification identifier.
215
+
216
+ Retrieves completed tasks, journal entries, and progress
217
+ to help craft meaningful PR descriptions.
218
+ """
219
+ start_time = time.perf_counter()
220
+ cli_ctx = get_context(ctx)
221
+ specs_dir = cli_ctx.specs_dir
222
+
223
+ if specs_dir is None:
224
+ emit_error(
225
+ "No specs directory found",
226
+ code="VALIDATION_ERROR",
227
+ error_type="validation",
228
+ remediation="Use --specs-dir option or set SDD_SPECS_DIR environment variable",
229
+ details={"hint": "Use --specs-dir or set SDD_SPECS_DIR"},
230
+ )
231
+ return
232
+
233
+ # Use prepare_review_context from core/review for consistent context gathering
234
+ from foundry_mcp.core.review import ReviewContext, prepare_review_context
235
+
236
+ review_ctx = prepare_review_context(
237
+ spec_id=spec_id,
238
+ specs_dir=specs_dir,
239
+ include_tasks=include_tasks,
240
+ include_journals=include_journals,
241
+ max_journal_entries=5,
242
+ )
243
+
244
+ if review_ctx is None:
245
+ emit_error(
246
+ f"Specification not found: {spec_id}",
247
+ code="SPEC_NOT_FOUND",
248
+ error_type="not_found",
249
+ remediation="Verify the spec ID exists using: sdd specs list",
250
+ details={"spec_id": spec_id},
251
+ )
252
+ return
253
+
254
+ review_ctx = cast(ReviewContext, review_ctx)
255
+
256
+ context: Dict[str, Any] = {
257
+ "spec_id": spec_id,
258
+ "title": review_ctx.title,
259
+ }
260
+
261
+ # Get progress stats from context
262
+ if include_progress:
263
+ context["progress"] = {
264
+ "total_tasks": review_ctx.progress.get("total_tasks", 0),
265
+ "completed_tasks": review_ctx.progress.get("completed_tasks", 0),
266
+ "percentage": review_ctx.progress.get("percentage", 0),
267
+ }
268
+
269
+ # Get completed tasks from context
270
+ if include_tasks:
271
+ context["completed_tasks"] = review_ctx.completed_tasks
272
+
273
+ # Get journal entries from context
274
+ if include_journals:
275
+ context["journals"] = review_ctx.journal_entries
276
+
277
+ duration_ms = (time.perf_counter() - start_time) * 1000
278
+
279
+ emit_success(
280
+ {
281
+ **context,
282
+ "telemetry": {"duration_ms": round(duration_ms, 2)},
283
+ }
284
+ )
285
+
286
+
287
+ @pr_group.command("status")
288
+ @click.pass_context
289
+ @cli_command("status")
290
+ @handle_keyboard_interrupt()
291
+ @with_sync_timeout(FAST_TIMEOUT, "PR status check timed out")
292
+ def pr_status_cmd(ctx: click.Context) -> None:
293
+ """Check prerequisites for PR creation."""
294
+ start_time = time.perf_counter()
295
+
296
+ # Check GitHub CLI
297
+ gh_available = False
298
+ gh_version = None
299
+ try:
300
+ result = subprocess.run(
301
+ ["gh", "--version"],
302
+ capture_output=True,
303
+ text=True,
304
+ timeout=5.0,
305
+ )
306
+ gh_available = result.returncode == 0
307
+ if gh_available:
308
+ gh_version = result.stdout.split("\n")[0].strip()
309
+ except (FileNotFoundError, subprocess.TimeoutExpired):
310
+ pass
311
+
312
+ # Check if authenticated
313
+ gh_authenticated = False
314
+ if gh_available:
315
+ try:
316
+ result = subprocess.run(
317
+ ["gh", "auth", "status"],
318
+ capture_output=True,
319
+ text=True,
320
+ timeout=5.0,
321
+ )
322
+ gh_authenticated = result.returncode == 0
323
+ except (FileNotFoundError, subprocess.TimeoutExpired):
324
+ pass
325
+
326
+ # Check LLM status
327
+ llm_status = _get_llm_status()
328
+
329
+ # Check git status
330
+ git_clean = False
331
+ try:
332
+ result = subprocess.run(
333
+ ["git", "status", "--porcelain"],
334
+ capture_output=True,
335
+ text=True,
336
+ timeout=5.0,
337
+ )
338
+ git_clean = result.returncode == 0 and not result.stdout.strip()
339
+ except (FileNotFoundError, subprocess.TimeoutExpired):
340
+ pass
341
+
342
+ duration_ms = (time.perf_counter() - start_time) * 1000
343
+
344
+ # Build status
345
+ prerequisites = {
346
+ "github_cli": {
347
+ "available": gh_available,
348
+ "version": gh_version,
349
+ "authenticated": gh_authenticated,
350
+ },
351
+ "git": {
352
+ "clean": git_clean,
353
+ },
354
+ "llm": llm_status,
355
+ }
356
+
357
+ ready = gh_available and gh_authenticated
358
+ recommendations = []
359
+
360
+ if not gh_available:
361
+ recommendations.append("Install GitHub CLI: https://cli.github.com/")
362
+ elif not gh_authenticated:
363
+ recommendations.append("Authenticate with: gh auth login")
364
+ if not git_clean:
365
+ recommendations.append("Commit or stash uncommitted changes")
366
+ if not llm_status.get("configured"):
367
+ recommendations.append("Configure LLM for enhanced PR descriptions")
368
+
369
+ emit_success(
370
+ {
371
+ "ready": ready,
372
+ "prerequisites": prerequisites,
373
+ "recommendations": recommendations,
374
+ "telemetry": {"duration_ms": round(duration_ms, 2)},
375
+ }
376
+ )
377
+
378
+
379
+ def _get_llm_status() -> dict:
380
+ """Get LLM configuration status."""
381
+ try:
382
+ from foundry_mcp.core.llm_config import get_llm_config
383
+
384
+ config = get_llm_config()
385
+ return {
386
+ "configured": config.get_api_key() is not None,
387
+ "provider": config.provider.value,
388
+ "model": config.get_model(),
389
+ }
390
+ except ImportError:
391
+ return {"configured": False, "error": "LLM config not available"}
392
+ except Exception as e:
393
+ return {"configured": False, "error": str(e)}