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,366 @@
1
+ """
2
+ Non-LLM review logic for SDD specifications.
3
+
4
+ Provides structural review capabilities that don't require AI/LLM integration.
5
+ For LLM-powered reviews, use the sdd-toolkit:sdd-plan-review skill.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from foundry_mcp.core.spec import load_spec, find_specs_directory
13
+ from foundry_mcp.core.validation import (
14
+ ValidationResult,
15
+ validate_spec,
16
+ calculate_stats,
17
+ SpecStats,
18
+ )
19
+ from foundry_mcp.core.progress import get_progress_summary, list_phases
20
+
21
+
22
+ # Review types that don't require LLM
23
+ QUICK_REVIEW_TYPES = ["quick"]
24
+
25
+ # Review types that require LLM
26
+ LLM_REQUIRED_TYPES = ["full", "security", "feasibility"]
27
+
28
+
29
+ @dataclass
30
+ class ReviewFinding:
31
+ """
32
+ A single review finding from structural analysis.
33
+ """
34
+ code: str # Finding code (e.g., "EMPTY_PHASE", "MISSING_ESTIMATES")
35
+ message: str # Human-readable description
36
+ severity: str # "error", "warning", "info"
37
+ category: str # "structure", "quality", "completeness", "metadata"
38
+ location: Optional[str] = None # Node ID or path where issue occurred
39
+ suggestion: Optional[str] = None # Suggested fix
40
+
41
+
42
+ @dataclass
43
+ class QuickReviewResult:
44
+ """
45
+ Result of a quick (non-LLM) structural review.
46
+ """
47
+ spec_id: str
48
+ title: str
49
+ review_type: str = "quick"
50
+ is_valid: bool = True
51
+ findings: List[ReviewFinding] = field(default_factory=list)
52
+ stats: Optional[SpecStats] = None
53
+ progress: Optional[Dict[str, Any]] = None
54
+ phases: Optional[List[Dict[str, Any]]] = None
55
+ summary: str = ""
56
+ error_count: int = 0
57
+ warning_count: int = 0
58
+ info_count: int = 0
59
+
60
+
61
+ @dataclass
62
+ class ReviewContext:
63
+ """
64
+ Context for review operations.
65
+
66
+ Provides spec data, progress, and other context needed for reviews.
67
+ """
68
+ spec_id: str
69
+ spec_data: Dict[str, Any]
70
+ title: str
71
+ progress: Dict[str, Any]
72
+ phases: List[Dict[str, Any]]
73
+ stats: SpecStats
74
+ validation: ValidationResult
75
+ completed_tasks: List[Dict[str, Any]]
76
+ journal_entries: List[Dict[str, Any]]
77
+
78
+
79
+ def get_llm_status() -> Dict[str, Any]:
80
+ """
81
+ Get LLM configuration status.
82
+
83
+ Returns:
84
+ Dict with configured, provider, and model info
85
+ """
86
+ try:
87
+ from foundry_mcp.core.llm_config import get_llm_config
88
+
89
+ config = get_llm_config()
90
+ return {
91
+ "configured": config.get_api_key() is not None,
92
+ "provider": config.provider.value,
93
+ "model": config.get_model(),
94
+ }
95
+ except ImportError:
96
+ return {"configured": False, "error": "LLM config not available"}
97
+ except Exception as e:
98
+ return {"configured": False, "error": str(e)}
99
+
100
+
101
+ def prepare_review_context(
102
+ spec_id: str,
103
+ specs_dir: Optional[Path] = None,
104
+ include_tasks: bool = True,
105
+ include_journals: bool = True,
106
+ max_journal_entries: int = 10,
107
+ ) -> Optional[ReviewContext]:
108
+ """
109
+ Prepare context for review operations.
110
+
111
+ Gathers spec data, validation results, progress, and other context
112
+ needed for both quick and LLM-powered reviews.
113
+
114
+ Args:
115
+ spec_id: Specification ID to review
116
+ specs_dir: Optional specs directory (auto-discovered if not provided)
117
+ include_tasks: Include completed task summaries
118
+ include_journals: Include recent journal entries
119
+ max_journal_entries: Maximum journal entries to include
120
+
121
+ Returns:
122
+ ReviewContext with all gathered data, or None if spec not found
123
+ """
124
+ # Discover specs directory if not provided
125
+ if specs_dir is None:
126
+ specs_dir = find_specs_directory()
127
+ if specs_dir is None:
128
+ return None
129
+
130
+ # Load spec
131
+ spec_data = load_spec(spec_id, specs_dir)
132
+ if spec_data is None:
133
+ return None
134
+
135
+ title = spec_data.get("title", "Untitled")
136
+ hierarchy = spec_data.get("hierarchy", {})
137
+
138
+ # Get validation results
139
+ validation = validate_spec(spec_data)
140
+
141
+ # Get stats
142
+ stats = calculate_stats(spec_data)
143
+
144
+ # Get progress
145
+ progress = get_progress_summary(spec_data)
146
+
147
+ # Get phases
148
+ phases = list_phases(spec_data)
149
+
150
+ # Get completed tasks
151
+ completed_tasks = []
152
+ if include_tasks:
153
+ for node_id, node in hierarchy.items():
154
+ if node.get("type") in ("task", "subtask", "verify"):
155
+ if node.get("status") == "completed":
156
+ completed_tasks.append({
157
+ "id": node_id,
158
+ "title": node.get("title", "Untitled"),
159
+ "type": node.get("type"),
160
+ "parent": node.get("parent"),
161
+ })
162
+
163
+ # Get journal entries
164
+ journal_entries = []
165
+ if include_journals:
166
+ journal = spec_data.get("journal", [])
167
+ # Get most recent entries
168
+ journal_entries = journal[-max_journal_entries:] if journal else []
169
+
170
+ return ReviewContext(
171
+ spec_id=spec_id,
172
+ spec_data=spec_data,
173
+ title=title,
174
+ progress=progress,
175
+ phases=phases,
176
+ stats=stats,
177
+ validation=validation,
178
+ completed_tasks=completed_tasks,
179
+ journal_entries=journal_entries,
180
+ )
181
+
182
+
183
+ def quick_review(
184
+ spec_id: str,
185
+ specs_dir: Optional[Path] = None,
186
+ ) -> QuickReviewResult:
187
+ """
188
+ Perform a quick structural review of a specification.
189
+
190
+ This is a non-LLM review that checks:
191
+ - Spec structure and validation
192
+ - Progress and phase organization
193
+ - Task completeness and estimates
194
+ - Common quality issues
195
+
196
+ Args:
197
+ spec_id: Specification ID to review
198
+ specs_dir: Optional specs directory
199
+
200
+ Returns:
201
+ QuickReviewResult with findings and stats
202
+ """
203
+ # Get review context
204
+ context = prepare_review_context(
205
+ spec_id=spec_id,
206
+ specs_dir=specs_dir,
207
+ include_tasks=True,
208
+ include_journals=False,
209
+ )
210
+
211
+ if context is None:
212
+ return QuickReviewResult(
213
+ spec_id=spec_id,
214
+ title="Unknown",
215
+ is_valid=False,
216
+ summary="Specification not found",
217
+ findings=[
218
+ ReviewFinding(
219
+ code="SPEC_NOT_FOUND",
220
+ message=f"Specification '{spec_id}' not found",
221
+ severity="error",
222
+ category="structure",
223
+ )
224
+ ],
225
+ error_count=1,
226
+ )
227
+
228
+ findings: List[ReviewFinding] = []
229
+
230
+ # Convert validation diagnostics to findings
231
+ for diag in context.validation.diagnostics:
232
+ findings.append(
233
+ ReviewFinding(
234
+ code=diag.code,
235
+ message=diag.message,
236
+ severity=diag.severity,
237
+ category=diag.category,
238
+ location=diag.location,
239
+ suggestion=diag.suggested_fix,
240
+ )
241
+ )
242
+
243
+ # Check for empty phases (phases with no children in the hierarchy)
244
+ hierarchy = context.spec_data.get("hierarchy", {})
245
+ for node_id, node in hierarchy.items():
246
+ if node.get("type") == "phase":
247
+ children = node.get("children", [])
248
+ if len(children) == 0:
249
+ findings.append(
250
+ ReviewFinding(
251
+ code="EMPTY_PHASE",
252
+ message=f"Phase '{node.get('title', node_id)}' has no tasks",
253
+ severity="warning",
254
+ category="structure",
255
+ location=node_id,
256
+ suggestion="Add tasks to this phase or remove it",
257
+ )
258
+ )
259
+
260
+ # Check for missing estimates
261
+ tasks_without_estimates = 0
262
+ for node_id, node in hierarchy.items():
263
+ if node.get("type") in ("task", "subtask"):
264
+ metadata = node.get("metadata", {})
265
+ if metadata.get("estimated_hours") is None:
266
+ tasks_without_estimates += 1
267
+
268
+ if tasks_without_estimates > 0:
269
+ findings.append(
270
+ ReviewFinding(
271
+ code="MISSING_ESTIMATES",
272
+ message=f"{tasks_without_estimates} task(s) are missing time estimates",
273
+ severity="info",
274
+ category="completeness",
275
+ suggestion="Add estimated_hours to task metadata for better planning",
276
+ )
277
+ )
278
+
279
+ # Check for tasks without file paths
280
+ tasks_without_files = 0
281
+ for node_id, node in hierarchy.items():
282
+ if node.get("type") in ("task", "subtask"):
283
+ metadata = node.get("metadata", {})
284
+ if not metadata.get("file_path"):
285
+ tasks_without_files += 1
286
+
287
+ if tasks_without_files > 0:
288
+ findings.append(
289
+ ReviewFinding(
290
+ code="MISSING_FILE_PATHS",
291
+ message=f"{tasks_without_files} task(s) are missing file path references",
292
+ severity="info",
293
+ category="completeness",
294
+ suggestion="Add file_path to task metadata for better navigation",
295
+ )
296
+ )
297
+
298
+ # Check for blocked tasks
299
+ blocked_count = 0
300
+ for node_id, node in hierarchy.items():
301
+ if node.get("status") == "blocked":
302
+ blocked_count += 1
303
+
304
+ if blocked_count > 0:
305
+ findings.append(
306
+ ReviewFinding(
307
+ code="BLOCKED_TASKS",
308
+ message=f"{blocked_count} task(s) are currently blocked",
309
+ severity="warning",
310
+ category="quality",
311
+ suggestion="Resolve blockers to continue progress",
312
+ )
313
+ )
314
+
315
+ # Check overall progress
316
+ progress = context.progress
317
+ percentage = progress.get("percentage", 0)
318
+
319
+ # Count severities
320
+ error_count = sum(1 for f in findings if f.severity == "error")
321
+ warning_count = sum(1 for f in findings if f.severity == "warning")
322
+ info_count = sum(1 for f in findings if f.severity == "info")
323
+
324
+ # Build summary
325
+ summary_parts = [
326
+ f"Reviewed '{context.title}' ({spec_id})",
327
+ f"Progress: {percentage:.0f}% ({progress.get('completed_tasks', 0)}/{progress.get('total_tasks', 0)} tasks)",
328
+ ]
329
+
330
+ if error_count > 0:
331
+ summary_parts.append(f"{error_count} error(s)")
332
+ if warning_count > 0:
333
+ summary_parts.append(f"{warning_count} warning(s)")
334
+ if info_count > 0:
335
+ summary_parts.append(f"{info_count} info finding(s)")
336
+
337
+ if error_count == 0 and warning_count == 0:
338
+ summary_parts.append("No critical issues found")
339
+
340
+ return QuickReviewResult(
341
+ spec_id=spec_id,
342
+ title=context.title,
343
+ review_type="quick",
344
+ is_valid=context.validation.is_valid,
345
+ findings=findings,
346
+ stats=context.stats,
347
+ progress=context.progress,
348
+ phases=context.phases,
349
+ summary=". ".join(summary_parts) + ".",
350
+ error_count=error_count,
351
+ warning_count=warning_count,
352
+ info_count=info_count,
353
+ )
354
+
355
+
356
+ def review_type_requires_llm(review_type: str) -> bool:
357
+ """
358
+ Check if a review type requires LLM.
359
+
360
+ Args:
361
+ review_type: Review type to check
362
+
363
+ Returns:
364
+ True if LLM is required, False for quick reviews
365
+ """
366
+ return review_type in LLM_REQUIRED_TYPES